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.
NextAuth session JWT cookie. Development: often next-auth.session-token.
Production (HTTPS): often __Secure-next-auth.session-token. Send the cookie your browser receives after sign-in.
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.
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.
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.
nullclears 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.
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.
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 kinds 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.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Documentation - Formbricks","colors":{"primary":"#00C4B8","light":"#00C4B8","dark":"#00C4B8"},"logo":{"light":"https://mintcdn.com/formbricks/wqEbm3FT1QK_6-vL/images/logo-light.svg?fit=max\u0026auto=format\u0026n=wqEbm3FT1QK_6-vL\u0026q=85\u0026s=365e5fb767b72b4fbbb2016860fd92ba","dark":"https://mintcdn.com/formbricks/wqEbm3FT1QK_6-vL/images/logo-dark.svg?fit=max\u0026auto=format\u0026n=wqEbm3FT1QK_6-vL\u0026q=85\u0026s=763690d005584ee5f4b4014615c3b98c"},"favicon":"https://mintcdn.com/formbricks/wqEbm3FT1QK_6-vL/images/favicon.svg?fit=max\u0026auto=format\u0026n=wqEbm3FT1QK_6-vL\u0026q=85\u0026s=01910ab43e3a3acc0aca5471235b90c5","navbar":{"links":[{"href":"https://github.com/formbricks/formbricks/discussions","label":"Support"}],"primary":{"type":"button","label":"Go to app","href":"https://app.formbricks.com"}},"footer":{"socials":{"github":"https://github.com/formbricks/formbricks","linkedin":"https://linkedin.com/company/formbricks","x":"https://x.com/formbricks"}},"integrations":{"posthog":{"apiKey":"phc_zJO1n3rjHlhqKiJ0gqLDwT4qvfWF1wzHDq2gfl0moQm","apiHost":"https://eu.i.posthog.com"}},"errors":{"404":{"redirect":true}},"contextual":{"options":["copy","view","chatgpt","claude","perplexity"],"display":"header"},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"formbricks","hasAudioNarration":false,"actualSubdomain":"formbricks","customDomains":["formbricks.com"],"gitSource":{"type":"github","owner":"formbricks","repo":"formbricks","deployBranch":"release/6.1","contentDirectory":"docs","isPrivate":false,"mountPath":"","label":""},"feedback":{"thumbs":true,"issues":true,"edits":true,"agentFeedback":false},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6aec6ba8981b8170d4f1db3c","clientVersion":"0.0.4087","searchFilterCounts":[{"language":"default","tag":"Surveys (Ask)","count":87},{"language":"default","tag":"Survey Features","count":71},{"language":"default","tag":"Self Hosting","count":53},{"language":"default","tag":"API v2 Reference (Beta)","count":41},{"language":"default","tag":"API v1 Reference","count":35},{"language":"default","tag":"Platform","count":28},{"language":"default","tag":"Configuration","count":28},{"language":"default","tag":"Platform Features","count":23},{"language":"default","tag":"Integrations","count":22},{"language":"default","tag":"Development","count":21}]},"assistantConfig":{},"basePath":"/docs","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"api-v3-reference/v3-responses/update-a-response","slug":"/api-v3-reference/v3-responses/update-a-response","pageMetadata":{"title":"Update a response","description":"Partially updates a response. Requires read-write access; the workspace is resolved from the response. An unknown or inaccessible `responseId` answers **403**.\n\n`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.\n\nEvery 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.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/responses/{responseId}","href":"/api-v3-reference/v3-responses/update-a-response","autogeneratedByOpenApi":true,"hidden":true},"description":{"compiledSource":"\"use strict\";\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n code: \"code\",\n p: \"p\",\n strong: \"strong\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsxs(_Fragment, {\n children: [_jsxs(_components.p, {\n children: [\"Partially updates a response. Requires read-write access; the workspace is resolved from the response. An unknown or inaccessible \", _jsx(_components.code, {\n children: \"responseId\"\n }), \" answers \", _jsx(_components.strong, {\n children: \"403\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.code, {\n children: \"PATCH\"\n }), \" is the only update verb — there is no \", _jsx(_components.code, {\n children: \"PUT\"\n }), \". Within it, \", _jsx(_components.code, {\n children: \"data\"\n }), \" is replaced wholesale rather than deep-merged, so send the complete answer map; \", _jsx(_components.code, {\n children: \"embeddedData\"\n }), \" merges by field name. Unknown top-level keys are rejected with \", _jsx(_components.strong, {\n children: \"400\"\n }), \" rather than ignored.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Every patch emits \", _jsx(_components.code, {\n children: \"responseUpdated\"\n }), \" and runs the response pipeline, including Hub ingestion. Transitioning \", _jsx(_components.code, {\n children: \"finished\"\n }), \" to \", _jsx(_components.code, {\n children: \"true\"\n }), \" additionally emits \", _jsx(_components.code, {\n children: \"responseFinished\"\n }), \"; patching a response that is already finished does not re-emit it.\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){return _jsx(_Fragment,{})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Update a response","description":"Partially updates a response. Requires read-write access; the workspace is resolved from the response. An unknown or inaccessible `responseId` answers **403**.\n\n`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.\n\nEvery 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.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/responses/{responseId}","href":"/api-v3-reference/v3-responses/update-a-response","autogeneratedByOpenApi":true,"hidden":true}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[],"codeExamples":{}},"jsLinks":["/docs/_mintlify/custom/formbricks/fd84136b00b6b9ed/formbricks.js"],"openApiReferenceData":{"operation":{"operationId":"updateResponseV3","summary":"Update a response","description":"Partially updates a response. Requires read-write access; the workspace is resolved from the response. An unknown or inaccessible `responseId` answers **403**.\n\n`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.\n\nEvery 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.\n","tags":["V3 Responses"],"parameters":["ef5e4c5c-db5d-43e3-a504-efbeb6500d58"],"requestBody":"be1656e0-a389-46f4-9ad6-17e2b919c4e8","responses":{"200":"faa9ac06-1479-4751-bfe7-032decf41eee","400":"363e39e5-ed7e-4da3-be17-9bc962c01d36","401":"5b6134bb-997d-4b5a-8bcb-3c48a8cc68a3","403":"3552e03a-3991-4c6c-a52f-61d59ea924b3","413":"46e5594d-28d8-49f2-9385-91c4e9f716d4","422":"4e72e8ba-0b6e-4269-9902-77e890445f90","429":"b1eb1de9-d55d-4645-93c9-5ada1b6ed1a4","500":"e2afaafd-9ce9-4159-b0cc-f6701fd1c90b"},"security":["98be720b-4c09-4f6c-af4f-6271c6b02080","db8f638c-cf5c-4177-9987-bb8d1ca1ffe2","175f5ced-7dd9-49de-8cfd-6f1252c9c53d"],"method":"patch","type":"path","servers":["5a1d53ae-163d-4303-ac56-7a378a4d43bc"],"path":"/api/v3/responses/{responseId}","baseUrlOptions":["https://app.formbricks.com"],"title":"Update a response","codeSamples":[],"requiredOnlyExamples":false,"requestExampleType":"generated","responseExampleType":"generated"},"dependencies":{"parameters":{"ef5e4c5c-db5d-43e3-a504-efbeb6500d58":{"in":"path","name":"responseId","required":true,"schema":{"type":"string","format":"cuid2","isRequired":true,"description":"Response identifier.","uniqueKey":"","typeLabel":"string\u003ccuid2>"},"description":"Response identifier."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"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.\n\n`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.\n\nv3 replaces on purpose, where v1 and v2 merge: a merge makes it impossible to delete an answer, which a correction flow needs.\n\n**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.\n\nNot 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.\n","minProperties":1,"properties":{"finished":{"type":"boolean","uniqueKey":"finished","typeLabel":"boolean"},"endingId":{"oneOf":[{"type":"string","uniqueKey":"endingId.oneOf.0","typeLabel":"string"},{"type":"null","uniqueKey":"endingId.oneOf.1","typeLabel":"null"}],"uniqueKey":"endingId","typeLabel":"string | null"},"language":{"oneOf":[{"type":"string","uniqueKey":"language.oneOf.0","typeLabel":"string"},{"type":"null","uniqueKey":"language.oneOf.1","typeLabel":"null"}],"uniqueKey":"language","typeLabel":"string | null"},"data":{"type":"object","description":"The same map on the way in, with the cardinality bounds a v3 write enforces.\n\nAt 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.\n\nDeliberately 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.\n","maxProperties":500,"additionalProperties":{"oneOf":[{"type":"string","description":"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.\n\nDeclared 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.\n","uniqueKey":"data.additionalProperties.oneOf.0","typeLabel":"string"},{"type":"number","description":"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.\n\nDeclared 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.\n","uniqueKey":"data.additionalProperties.oneOf.1","typeLabel":"number"},{"type":"array","items":{"type":"string","uniqueKey":"data.additionalProperties.oneOf.2.items.[INDEX]","typeLabel":"string"},"description":"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.\n\nDeclared 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.\n","uniqueKey":"data.additionalProperties.oneOf.2","typeLabel":"string[]"},{"type":"object","description":"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.\n\nDeclared 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.\n","additionalProperties":{"type":"string","uniqueKey":"data.additionalProperties.oneOf.3.additionalProperties","typeLabel":"string"},"uniqueKey":"data.additionalProperties.oneOf.3","typeLabel":"object"}],"uniqueKey":"data.additionalProperties","typeLabel":"string | number | string[] | object"},"uniqueKey":"data","typeLabel":"object"},"embeddedData":{"type":"object","description":"Embedded Data values to write, **keyed by field name**.\n\nThis 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.\n\n**Which kinds are writable:**\n\n- `ingested` (hidden fields) — accepted, through the same ingest contract the SDK uses: the name\n must be declared on the survey, the value is coerced to the field's declared `type`, a `locked`\n field ignores the write, and the whole payload is bounded. A name the survey does not declare is\n a **422** rather than a silent drop — knowing whether a name is declared means loading the\n survey, and these operations route every failure that needs stored state to 422.\n\n- `computed` (variables) — accepted, because a management caller correcting a response needs to,\n and v1 and v2 both allow it today.\n\n- `reserved` — refused. Auto-captured context describes the submission environment, and a\n caller-supplied value would be fiction. The three exceptions are `source`, `url` and `action`,\n which `ResponseMetaInput` accepts for exactly the replay case.\n\n\nValues 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.\n\n`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.\n\n**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.\n\nReserved 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.\n\n**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`.\n","maxProperties":500,"additionalProperties":{"oneOf":[{"type":"string","uniqueKey":"embeddedData.additionalProperties.oneOf.0","typeLabel":"string"},{"type":"number","uniqueKey":"embeddedData.additionalProperties.oneOf.1","typeLabel":"number"},{"type":"boolean","uniqueKey":"embeddedData.additionalProperties.oneOf.2","typeLabel":"boolean"},{"type":"null","uniqueKey":"embeddedData.additionalProperties.oneOf.3","typeLabel":"null"}],"uniqueKey":"embeddedData.additionalProperties","typeLabel":"string | number | boolean | null"},"uniqueKey":"embeddedData","typeLabel":"object"},"tags":{"type":"array","maxItems":100,"description":"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`.","items":{"type":"string","format":"cuid2","uniqueKey":"tags.items.[INDEX]","typeLabel":"string\u003ccuid2>"},"uniqueKey":"tags","typeLabel":"string\u003ccuid2>[]"}},"additionalProperties":false,"uniqueKey":"","typeLabel":"object"},"examples":{"finishResponse":{"title":"finishResponse","value":{"finished":true,"endingId":"cmpthankyou00000000000000"}},"replaceAnswers":{"title":"replaceAnswers","description":"`data` is not merged. Anything omitted from this map is removed from the response.\n","value":{"data":{"nps_score":8,"what_could_be_better":"Etwas schneller wäre gut."}}},"retagResponse":{"title":"retagResponse","value":{"tags":["cltg1234567890123456789012"]}}},"exampleType":"examples"}},"exampleType":"examples"},"responses":{"faa9ac06-1479-4751-bfe7-032decf41eee":{"description":"Response updated successfully","headers":{"X-Request-Id":{"schema":{"type":"string"},"description":"Request correlation ID"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","description":"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.\n\nIt 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\".\n\nThey 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.\n\nValues 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.\n\nNote 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.\n\nTo 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.\n\n**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.\n","required":["contact","displayId","singleUseId","data","id","surveyId","surveyName","workspaceId","createdAt","updatedAt","finished","endingId","language","resolution","answers","embeddedData","unresolved","tags"],"properties":{"id":{"readOnly":true,"type":"string","format":"cuid2","description":"Response identifier.","isRequired":true,"uniqueKey":"data.id","typeLabel":"string\u003ccuid2>"},"surveyId":{"type":"string","format":"cuid2","description":"The survey this response belongs to.","isRequired":true,"uniqueKey":"data.surveyId","typeLabel":"string\u003ccuid2>"},"surveyName":{"readOnly":true,"type":"string","description":"The survey's current name, denormalized into the payload so a consumer listing responses need not resolve survey names separately.","isRequired":true,"uniqueKey":"data.surveyName","typeLabel":"string"},"workspaceId":{"readOnly":true,"type":"string","format":"cuid2","description":"The owning workspace, resolved through the survey. Responses carry no workspace column of their own; this is a derived field.","isRequired":true,"uniqueKey":"data.workspaceId","typeLabel":"string\u003ccuid2>"},"createdAt":{"readOnly":true,"type":"string","format":"date-time","description":"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.\n","isRequired":true,"uniqueKey":"data.createdAt","typeLabel":"string\u003cdate-time>"},"updatedAt":{"readOnly":true,"type":"string","format":"date-time","description":"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.","isRequired":true,"uniqueKey":"data.updatedAt","typeLabel":"string\u003cdate-time>"},"finished":{"type":"boolean","description":"Whether the respondent reached an ending.","isRequired":true,"uniqueKey":"data.finished","typeLabel":"boolean"},"endingId":{"oneOf":[{"type":"string","description":"The ending card the respondent reached. `null` for unfinished responses.","isRequired":true,"uniqueKey":"data.endingId.oneOf.0","typeLabel":"string"},{"type":"null","description":"The ending card the respondent reached. `null` for unfinished responses.","isRequired":true,"uniqueKey":"data.endingId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.endingId","typeLabel":"string | null"},"language":{"oneOf":[{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.language.oneOf.0","typeLabel":"string"},{"type":"null","description":"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.","isRequired":true,"uniqueKey":"data.language.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.language","typeLabel":"string | null"},"resolution":{"type":"object","description":"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.\n","required":["labelPolicy","labelsLanguage","surveyUpdatedAt"],"properties":{"labelPolicy":{"type":"string","enum":["currentSurveyDefinition"],"description":"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.\n","isRequired":true,"uniqueKey":"data.resolution.labelPolicy","typeLabel":"enum\u003cstring>"},"labelsLanguage":{"oneOf":[{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.resolution.labelsLanguage.oneOf.0","typeLabel":"string"},{"type":"null","description":"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.","isRequired":true,"uniqueKey":"data.resolution.labelsLanguage.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.resolution.labelsLanguage","typeLabel":"string | null"},"surveyUpdatedAt":{"type":"string","format":"date-time","description":"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.","isRequired":true,"uniqueKey":"data.resolution.surveyUpdatedAt","typeLabel":"string\u003cdate-time>"}},"additionalProperties":false,"readOnly":true,"isRequired":true,"uniqueKey":"data.resolution","typeLabel":"object"},"answers":{"readOnly":true,"type":"array","description":"One entry per element the respondent reached, in survey order. Empty for a response that recorded no answers.","items":{"oneOf":[{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["valueText","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.0.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.0.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["openText"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.0.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.0.elementLabel","typeLabel":"string"},"valueText":{"type":"string","description":"The submitted text, verbatim.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.0.valueText","typeLabel":"string"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.0.durationSeconds","typeLabel":"number"},"inputType":{"type":"string","enum":["text","email","url","number","phone"],"description":"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`.","uniqueKey":"data.answers.items.[INDEX].oneOf.0.inputType","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["valueNumber","range","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["nps","rating","csat","ces"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.elementLabel","typeLabel":"string"},"valueNumber":{"type":"number","description":"The submitted score.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.valueNumber","typeLabel":"number"},"range":{"type":"object","description":"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.","required":["min","max"],"properties":{"min":{"type":"number","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.range.min","typeLabel":"number"},"max":{"type":"number","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.range.max","typeLabel":"number"}},"additionalProperties":false,"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.1.range","typeLabel":"object"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.1.durationSeconds","typeLabel":"number"},"scale":{"type":"string","enum":["number","smiley","star"],"description":"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.","uniqueKey":"data.answers.items.[INDEX].oneOf.1.scale","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["valueBoolean","rawValue","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["consent","cta"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.elementLabel","typeLabel":"string"},"valueBoolean":{"type":"boolean","description":"Whether the respondent accepted (`consent`) or clicked through (`cta`). Derived from the stored token, which is why `rawValue` is returned alongside it.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.valueBoolean","typeLabel":"boolean"},"rawValue":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.2.rawValue","typeLabel":"string"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.2.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["selections","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["multipleChoiceSingle","multipleChoiceMulti","pictureSelection","ranking"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.elementLabel","typeLabel":"string"},"selections":{"type":"array","description":"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`.","items":{"type":"object","description":"One selected option within a choice-style answer. `optionId` plus `rawValue` is deliberately a two-field split — the same split the Hub stores as `value_id` plus `value_text` — because option ids are stable while labels are editable and language-dependent. Consumers that need to group or compare across time should key on `optionId`; consumers rendering to a human should use `optionLabel`.\n","required":["optionId","optionLabel","rawValue","match"],"properties":{"optionId":{"oneOf":[{"type":"string","description":"Resolved option id, or the literal `other` for a write-in. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionId.oneOf.0","typeLabel":"string"},{"type":"null","description":"Resolved option id, or the literal `other` for a write-in. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionId","typeLabel":"string | null"},"optionLabel":{"oneOf":[{"type":"string","description":"Option label in the resolved language. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionLabel.oneOf.0","typeLabel":"string"},{"type":"null","description":"Option label in the resolved language. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionLabel.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].optionLabel","typeLabel":"string | null"},"rawValue":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].rawValue","typeLabel":"string"},"match":{"type":"string","enum":["exact","label","other","unmatched"],"description":"How a stored raw value was resolved against the current survey definition. Precedence is fixed and applied in this order:\n\n- `exact` — the stored value matched an option **id** in the current definition.\n- `label` — the stored value matched an option's **label text** (ids are stable, labels are\n editable and language-dependent, so a label match is weaker evidence).\n\n- `other` — the value arrived through the element's **Other** free-text input. Only reported when\n the element actually has that input enabled and the value was submitted through it.\n\n- `unmatched` — no option resolves. The raw value is still returned; it is **not** silently\n reclassified as `other`, because a *renamed* option is also unmatched text and filing it as a\n write-in would misattribute the answer.\n","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].match","typeLabel":"enum\u003cstring>"},"rank":{"type":"integer","minimum":1,"description":"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.","uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX].rank","typeLabel":"integer"}},"additionalProperties":false,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.3.selections","typeLabel":"object[]"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.3.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["valueDate","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.4.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.4.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["date"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.4.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.4.elementLabel","typeLabel":"string"},"valueDate":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.4.valueDate","typeLabel":"string"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.4.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["fileUrls","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.5.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.5.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["fileUpload"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.5.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.5.elementLabel","typeLabel":"string"},"fileUrls":{"type":"array","description":"The uploaded files, **as stored** — workspace-relative paths, not absolute URLs, and neither resolved nor signed.\n\nA 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.\n\nNot publicly fetchable in any case: access follows the same authorization as the response, and deleting the response deletes the underlying files.\n","items":{"type":"string","uniqueKey":"data.answers.items.[INDEX].oneOf.5.fileUrls.items.[INDEX]","typeLabel":"string"},"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.5.fileUrls","typeLabel":"string[]"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.5.durationSeconds","typeLabel":"number"},"fileCount":{"type":"integer","minimum":0,"description":"Convenience count of `fileUrls`, so a list consumer need not materialize the array.","uniqueKey":"data.answers.items.[INDEX].oneOf.5.fileCount","typeLabel":"integer"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["booked","rawValue","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["cal"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.elementLabel","typeLabel":"string"},"booked":{"type":"boolean","description":"Whether the respondent completed a booking.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.booked","typeLabel":"boolean"},"rawValue":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.6.rawValue","typeLabel":"string"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.6.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["rows","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["matrix"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.elementLabel","typeLabel":"string"},"rows":{"type":"array","description":"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`.","items":{"type":"object","required":["rawKey","rowLabel","rawValue","columnLabel","match"],"properties":{"rawKey":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rawKey","typeLabel":"string"},"rowLabel":{"type":"string","description":"Row label in the resolved language. Falls back to `rawKey` when the row no longer resolves.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rowLabel","typeLabel":"string"},"columnLabel":{"oneOf":[{"type":"string","description":"Column label in the resolved language. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnLabel.oneOf.0","typeLabel":"string"},{"type":"null","description":"Column label in the resolved language. `null` when `match` is `unmatched`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnLabel.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnLabel","typeLabel":"string | null"},"rawValue":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rawValue","typeLabel":"string"},"match":{"type":"string","enum":["exact","label","other","unmatched"],"description":"How a stored raw value was resolved against the current survey definition. Precedence is fixed and applied in this order:\n\n- `exact` — the stored value matched an option **id** in the current definition.\n- `label` — the stored value matched an option's **label text** (ids are stable, labels are\n editable and language-dependent, so a label match is weaker evidence).\n\n- `other` — the value arrived through the element's **Other** free-text input. Only reported when\n the element actually has that input enabled and the value was submitted through it.\n\n- `unmatched` — no option resolves. The raw value is still returned; it is **not** silently\n reclassified as `other`, because a *renamed* option is also unmatched text and filing it as a\n write-in would misattribute the answer.\n","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].match","typeLabel":"enum\u003cstring>"},"rowId":{"oneOf":[{"type":"string","description":"Row id resolved from the current definition, or `null` when the stored key matches no current row. Derived, never stored.","uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rowId.oneOf.0","typeLabel":"string"},{"type":"null","description":"Row id resolved from the current definition, or `null` when the stored key matches no current row. Derived, never stored.","uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rowId.oneOf.1","typeLabel":"null"}],"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].rowId","typeLabel":"string | null"},"columnId":{"oneOf":[{"type":"string","description":"Column id resolved from the current definition, or `null` when it no longer resolves. Derived, never stored.","uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnId.oneOf.0","typeLabel":"string"},{"type":"null","description":"Column id resolved from the current definition, or `null` when it no longer resolves. Derived, never stored.","uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnId.oneOf.1","typeLabel":"null"}],"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX].columnId","typeLabel":"string | null"}},"additionalProperties":false,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.7.rows","typeLabel":"object[]"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.7.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","description":"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.\n\nAnswers 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[]`.\n\nSeveral 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.\n\nOnly elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.\n","required":["fields","position","elementId","elementType","elementLabel"],"properties":{"position":{"type":"integer","minimum":1,"description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.position","typeLabel":"integer"},"elementId":{"type":"string","description":"Stable element id, as authored on the survey.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.elementId","typeLabel":"string"},"elementType":{"type":"string","description":"The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.","enum":["address","contactInfo"],"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.elementType","typeLabel":"enum\u003cstring>"},"elementLabel":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.elementLabel","typeLabel":"string"},"fields":{"type":"array","description":"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.\n","items":{"type":"object","required":["slot","fieldId","fieldLabel","valueText"],"properties":{"slot":{"type":"integer","minimum":0,"description":"0-based index of this sub-field in the stored array. Reconstruct the stored value by ordering on this and taking `valueText`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields.items.[INDEX].slot","typeLabel":"integer"},"fieldId":{"type":"string","enum":["addressLine1","addressLine2","city","state","zip","country","firstName","lastName","email","phone","company"],"description":"Stable sub-field id. `address` uses `addressLine1`, `addressLine2`, `city`, `state`, `zip`, `country` in that order; `contactInfo` uses `firstName`, `lastName`, `email`, `phone`, `company`.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields.items.[INDEX].fieldId","typeLabel":"enum\u003cstring>"},"fieldLabel":{"type":"string","description":"Sub-field label in the resolved language.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields.items.[INDEX].fieldLabel","typeLabel":"string"},"valueText":{"type":"string","description":"The submitted value, verbatim. `\"\"` when the respondent left it blank.","isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields.items.[INDEX].valueText","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.answers.items.[INDEX].oneOf.8.fields","typeLabel":"object[]"},"durationSeconds":{"type":"number","minimum":0,"maximum":86400,"description":"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.\n\nThe 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.\n","uniqueKey":"data.answers.items.[INDEX].oneOf.8.durationSeconds","typeLabel":"number"}},"uniqueKey":"data.answers.items.[INDEX].oneOf.8","typeLabel":"object"}],"uniqueKey":"data.answers.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.answers","typeLabel":"object[]"},"embeddedData":{"readOnly":true,"type":"array","description":"Hidden fields, variables and auto-captured context, as one collection. Empty when the response carries none.","items":{"type":"object","description":"One piece of non-answer data carried by the response. Survey **variables**, **hidden fields** and **auto-captured context** are one concept here rather than three parallel ones: they all have a key, a provenance, a declared type and a label, and consumers treat them alike.\n\nAuto-captured (`reserved`) entries are drawn from the Embedded Data field catalog, which declares a privacy policy per field (`keep` / `drop` / `redactQuery`). Two of those act **before** this API sees the response, and one is a rule this API adds:\n\n- `drop` is an **ingest-time** policy, not a filter applied here: when the survey has \"Anonymize\n responses\" enabled the field is never captured, so it is absent from the response for every\n consumer. With anonymization off it is captured and appears here normally.\n\n- `redactQuery` is applied on **every** read, anonymization or not — `url` and `pageReferrer` carry\n their query string stripped.\n\n- `ipAddress` is **never** projected by this API, in any view. That is a v3 rule rather than a\n catalog behaviour: the catalog reads the field, and the dashboard shows it. Do not infer the same\n suppression for the other `drop` fields.\n\n\nWhere a survey's own field and a catalog entry share a name, both appear — the declared one as `ingested` or `computed`, the auto-captured one as `reserved` — and `kind` is what tells them apart. Neither shadows the other, because each is a real value the response carries and dropping one would hide data the author can see elsewhere.\n\nWhich catalog entries appear at all is a third axis, `display`, and this collection carries the ones that are not `none`. The `none` set is the response's own identity and timing — `responseId`, `surveyId`, `finished`, `language`, `durationSeconds`, `startedAt`, `finishedAt` — every one of which is already a first-class field on the response. Without that filter they would each appear twice in the same payload, once at the top level and once here.\n","required":["key","kind","type","label","value"],"properties":{"key":{"type":"string","description":"The field's **name** — what an author calls it, and the key `embeddedData` on a write accepts.\n\nNot 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.\n\n**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.\n","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].key","typeLabel":"string"},"kind":{"type":"string","enum":["ingested","computed","reserved"],"description":"Provenance of the value:\n\n- `ingested` — supplied by the caller or the embedding page (hidden fields).\n- `computed` — derived during the submission by survey logic (variables).\n- `reserved` — auto-captured context from the submission environment (for example `country`,\n `deviceType`), drawn from the Embedded Data field catalog.\n","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].kind","typeLabel":"enum\u003cstring>"},"type":{"type":"string","enum":["string","number","boolean","date"],"description":"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.","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].type","typeLabel":"enum\u003cstring>"},"label":{"type":"string","description":"Human-readable name for the key, in the resolved language where one exists.","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].label","typeLabel":"string"},"value":{"oneOf":[{"type":"string","description":"The value, matching `type`. `date` values are ISO-8601 strings.\n\nAlways 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.\n\nThere 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.","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].value.oneOf.0","typeLabel":"string"},{"type":"number","description":"The value, matching `type`. `date` values are ISO-8601 strings.\n\nAlways 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.\n\nThere 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.","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].value.oneOf.1","typeLabel":"number"},{"type":"boolean","description":"The value, matching `type`. `date` values are ISO-8601 strings.\n\nAlways 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.\n\nThere 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.","isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].value.oneOf.2","typeLabel":"boolean"}],"isRequired":true,"uniqueKey":"data.embeddedData.items.[INDEX].value","typeLabel":"string | number | boolean"}},"additionalProperties":false,"uniqueKey":"data.embeddedData.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.embeddedData","typeLabel":"object[]"},"unresolved":{"readOnly":true,"type":"array","description":"Stored data whose element or key no longer exists in the survey definition. Empty in the normal case.","items":{"type":"object","description":"Stored data that nothing in the current survey definition can account for. Deleting an element, a hidden field or a variable leaves its values behind on already-collected responses and nothing cleans them up, so the payload reports them here instead of dropping them silently or faking them into `answers[]` without a type or a label. Consumers doing analysis can ignore this array; consumers doing export or migration should not.\n\n**A declared field's `defaultValue` takes precedence over anything unreadable stored under its key**, and that is worth stating because this collection otherwise reads as exhaustive. Resolution falls back to the default before a value is ever judged unreadable, so a field that declares one always resolves — and the stored bytes, whatever their shape, are reported nowhere. Only a field with **no** `defaultValue` can reach `valueShapeMismatch`.\nA **scalar** under a defaultless field is not reported either, and the cause is the reporting guard rather than `locked`. Only `resolveEmbeddedValue` consults the lock; the guard that decides what reaches this collection admits objects only. So an uncoercible scalar — `\"yes\"` under a boolean field — resolves to nothing and is then excluded for being a scalar, lock or no lock. A non-scalar under a lockless, defaultless field is what does reach `valueShapeMismatch`. Neither omission is a silent drop by accident — one follows from resolution order, the other from the reporting guard — and both are written down so a consumer does not read `unresolved[]` as a guarantee that nothing was left behind. If either proves to matter in practice a reason code can be added without breaking anyone, whereas removing one could not — which is why this is documentation rather than a guess at a fifth enum member.\n","required":["key","rawValue","reason"],"properties":{"key":{"type":"string","description":"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.","isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].key","typeLabel":"string"},"rawValue":{"oneOf":[{"type":"string","description":"The stored value, returned verbatim and untyped because nothing describes it any more.","isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.0","typeLabel":"string"},{"type":"number","description":"The stored value, returned verbatim and untyped because nothing describes it any more.","isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.1","typeLabel":"number"},{"type":"array","items":{"type":"string","uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.2.items.[INDEX]","typeLabel":"string"},"description":"The stored value, returned verbatim and untyped because nothing describes it any more.","isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.2","typeLabel":"string[]"},{"type":"object","description":"The stored value, returned verbatim and untyped because nothing describes it any more.","additionalProperties":{"type":"string","uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.3.additionalProperties","typeLabel":"string"},"isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].rawValue.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].rawValue","typeLabel":"string | number | string[] | object"},"reason":{"type":"string","enum":["elementNotInSurvey","variableNotInSurvey","valueShapeMismatch"],"description":"Why the entry could not be resolved:\n\n- `elementNotInSurvey` — nothing in the current definition claims this key. It covers a deleted\n element **and** a deleted hidden field, because the two cannot be told apart: an answer is\n stored under its element id and a hidden field under its name, in one shared map, and storage\n keeps no record of which kind a vanished key used to be.\n\n- `variableNotInSurvey` — no variable with this id exists in the current definition. Variables\n *are* distinguishable, because they live in their own map keyed by cuid rather than sharing\n the answer map.\n\n- `valueShapeMismatch` — the key resolves to a current element **or a declared Embedded Data\n field**, but the stored value's shape cannot be read as an\n answer of that element's type (for example a string where the element expects a selection\n list). Signals element-type churn rather than deletion.\n","isRequired":true,"uniqueKey":"data.unresolved.items.[INDEX].reason","typeLabel":"enum\u003cstring>"}},"additionalProperties":false,"uniqueKey":"data.unresolved.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.unresolved","typeLabel":"object[]"},"tags":{"type":"array","description":"Tags applied to this response, as `{ id, name }` references. Empty when the response is untagged.","items":{"type":"object","description":"A tag applied to a response, as a lightweight reference. Deliberately **not** the full `TagResource`: that shape carries a `count` of how many responses hold the tag, which would mean an extra aggregate per tag on every row of every page. Read tags in full — with counts and timestamps — from `GET /api/v3/tags`.\n","required":["id","name"],"properties":{"id":{"type":"string","format":"cuid2","isRequired":true,"uniqueKey":"data.tags.items.[INDEX].id","typeLabel":"string\u003ccuid2>"},"name":{"type":"string","description":"Tag label as shown in the app.","isRequired":true,"uniqueKey":"data.tags.items.[INDEX].name","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.tags.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"data.tags","typeLabel":"object[]"},"data":{"type":"object","description":"Answers in their **stored** form — byte-for-byte the same map `POST /api/v3/responses` and `PATCH /api/v3/responses/{responseId}` accept. Returned so that a read can be edited and written straight back: take this map, change what you need, and `PATCH` it. Without it a client wanting to correct one answer would have to rebuild the map from `answers[]`, which is possible but is work the server can simply not impose.\n\n`answers[]` remains the field to read — it is typed, labelled and ordered, and it separates answers from hidden fields. This is the same data in the shape the write side speaks, offered alongside rather than instead.\n\nPresent on the detailed view only. The list view omits it deliberately: on a page of 250 it would carry every answer twice, and a bulk reader is not editing.\n","additionalProperties":{"oneOf":[{"type":"string","description":"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.\n\nDeclared 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.\n","uniqueKey":"data.data.additionalProperties.oneOf.0","typeLabel":"string"},{"type":"number","description":"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.\n\nDeclared 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.\n","uniqueKey":"data.data.additionalProperties.oneOf.1","typeLabel":"number"},{"type":"array","items":{"type":"string","uniqueKey":"data.data.additionalProperties.oneOf.2.items.[INDEX]","typeLabel":"string"},"description":"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.\n\nDeclared 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.\n","uniqueKey":"data.data.additionalProperties.oneOf.2","typeLabel":"string[]"},{"type":"object","description":"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.\n\nDeclared 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.\n","additionalProperties":{"type":"string","uniqueKey":"data.data.additionalProperties.oneOf.3.additionalProperties","typeLabel":"string"},"uniqueKey":"data.data.additionalProperties.oneOf.3","typeLabel":"object"}],"uniqueKey":"data.data.additionalProperties","typeLabel":"string | number | string[] | object"},"isRequired":true,"uniqueKey":"data.data","typeLabel":"object"},"contact":{"oneOf":[{"type":"object","description":"The contact that submitted the response, or `null` when the response is anonymous. Present on the detailed view only.","required":["id"],"properties":{"id":{"type":"string","format":"cuid2","description":"Contact identifier.","isRequired":true,"uniqueKey":"data.contact.oneOf.0.id","typeLabel":"string\u003ccuid2>"},"userId":{"oneOf":[{"type":"string","description":"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.","uniqueKey":"data.contact.oneOf.0.userId.oneOf.0","typeLabel":"string"},{"type":"null","description":"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.","uniqueKey":"data.contact.oneOf.0.userId.oneOf.1","typeLabel":"null"}],"uniqueKey":"data.contact.oneOf.0.userId","typeLabel":"string | null"}},"additionalProperties":false,"isRequired":true,"uniqueKey":"data.contact.oneOf.0","typeLabel":"object"},{"type":"null","description":"The contact that submitted the response, or `null` when the response is anonymous. Present on the detailed view only.","isRequired":true,"uniqueKey":"data.contact.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.contact","typeLabel":"object | null"},"displayId":{"oneOf":[{"type":"string","description":"The display (survey impression) this response was submitted against, when one was recorded.","isRequired":true,"uniqueKey":"data.displayId.oneOf.0","typeLabel":"string"},{"type":"null","description":"The display (survey impression) this response was submitted against, when one was recorded.","isRequired":true,"uniqueKey":"data.displayId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.displayId","typeLabel":"string | null"},"singleUseId":{"oneOf":[{"type":"string","description":"The single-use link id this response was submitted through, when the survey uses single-use links.","isRequired":true,"uniqueKey":"data.singleUseId.oneOf.0","typeLabel":"string"},{"type":"null","description":"The single-use link id this response was submitted through, when the survey uses single-use links.","isRequired":true,"uniqueKey":"data.singleUseId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.singleUseId","typeLabel":"string | null"},"durationSeconds":{"readOnly":true,"type":"number","minimum":0,"description":"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.\n\nSumming 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.","uniqueKey":"data.durationSeconds","typeLabel":"number"}},"isRequired":true,"uniqueKey":"data","typeLabel":"object"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"data":{"id":"\u003cstring>","surveyId":"\u003cstring>","surveyName":"\u003cstring>","workspaceId":"\u003cstring>","createdAt":"2023-11-07T05:31:56Z","updatedAt":"2023-11-07T05:31:56Z","finished":true,"endingId":"\u003cstring>","language":"\u003cstring>","resolution":{"labelPolicy":"currentSurveyDefinition","labelsLanguage":"\u003cstring>","surveyUpdatedAt":"2023-11-07T05:31:56Z"},"answers":[{"position":2,"elementId":"\u003cstring>","elementType":"openText","elementLabel":"\u003cstring>","valueText":"\u003cstring>","durationSeconds":43200,"inputType":"text"}],"embeddedData":[{"key":"\u003cstring>","kind":"ingested","type":"string","label":"\u003cstring>","value":"\u003cstring>"}],"unresolved":[{"key":"\u003cstring>","rawValue":"\u003cstring>","reason":"elementNotInSurvey"}],"tags":[{"id":"\u003cstring>","name":"\u003cstring>"}],"data":{},"contact":{"id":"\u003cstring>","userId":"\u003cstring>"},"displayId":"\u003cstring>","singleUseId":"\u003cstring>","durationSeconds":1}}}},"exampleType":"generated"}},"exampleType":"generated"},"363e39e5-ed7e-4da3-be17-9bc962c01d36":{"description":"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.\n","content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"5b6134bb-997d-4b5a-8bcb-3c48a8cc68a3":{"description":"Not authenticated (no valid session or API key).","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"example":"Bearer realm=\"formbricks\"","description":"Authentication scheme challenge. RFC 9110 §15.5.2 asks a 401 for a challenge \"applicable to the target resource\", and Bearer is applicable here because these operations accept the API key as `Authorization: Bearer \u003ckey>` as well as `x-api-key`. Session-cookie-only operations send no challenge — see `V3UnauthorizedSession`. The MCP endpoints send a richer OAuth challenge carrying `resource_metadata` and `scope`."},"X-Request-Id":{"schema":{"type":"string"},"description":"Request correlation ID"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"}},"content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"3552e03a-3991-4c6c-a52f-61d59ea924b3":{"description":"Forbidden — no workspace access, or resource does not exist (404 not used; avoids existence leak).","headers":{"X-Request-Id":{"schema":{"type":"string"},"description":"Request correlation ID"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"}},"content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"46e5594d-28d8-49f2-9385-91c4e9f716d4":{"description":"Payload Too Large — the request body exceeds the 2 MB limit.","content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"4e72e8ba-0b6e-4269-9902-77e890445f90":{"description":"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.","content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"b1eb1de9-d55d-4645-93c9-5ada1b6ed1a4":{"description":"Rate limit exceeded.","headers":{"X-Request-Id":{"schema":{"type":"string"},"description":"Request correlation ID"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the current rate-limit window resets."}},"content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"e2afaafd-9ce9-4159-b0cc-f6701fd1c90b":{"description":"Internal Server Error.","headers":{"X-Request-Id":{"schema":{"type":"string"},"description":"Request correlation ID"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"}},"content":{"application/problem+json":{"schema":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs (`application/problem+json`). Responses typically include a machine-readable `code` field alongside `title`, `status`, `detail`, and `requestId`.\n\nBranch on `code`, not on `detail` or `title`: `code` is stable and locale-independent, whereas `detail` is prose that may be reworded. No problem sets `type`, so per RFC 9457 §4.2.1 `title` is always the HTTP reason phrase for `status` and carries no information of its own.\n\nMember naming is frozen as published, mixed conventions included (`requestId` alongside `invalid_params` and `details.resource_type`): renaming any of them would break every client that reads them.","required":["title","status","detail","requestId"],"properties":{"title":{"type":"string","isRequired":true,"uniqueKey":"title","typeLabel":"string"},"status":{"type":"integer","isRequired":true,"uniqueKey":"status","typeLabel":"integer"},"detail":{"type":"string","isRequired":true,"uniqueKey":"detail","typeLabel":"string"},"requestId":{"type":"string","isRequired":true,"uniqueKey":"requestId","typeLabel":"string"},"type":{"type":"string","format":"uri","uniqueKey":"type","typeLabel":"string\u003curi>"},"instance":{"type":"string","uniqueKey":"instance","typeLabel":"string"},"code":{"type":"string","enum":["ai_features_not_enabled","ai_generated_payload_invalid","ai_instance_not_configured","ai_output_too_long","ai_provider_auth_failed","ai_smart_tools_disabled","bad_gateway","bad_request","conflict","custom_css_plan_required","forbidden","internal_server_error","invalid_workflow_state","not_authenticated","not_found","payload_too_large","projection_pending","service_unavailable","stored_survey_invalid","survey_not_workspace_visible","too_many_requests","unprocessable_content","visibility_blocked_by_connections","visibility_change_not_allowed","visibility_not_enabled","workflow_not_executable","workspace_survey_limit_reached"],"uniqueKey":"code","typeLabel":"enum\u003cstring>"},"details":{"type":"object","uniqueKey":"details","typeLabel":"object"},"invalid_params":{"type":"array","items":{"type":"object","description":"Field-level validation error. `name` and `reason` are always intended for humans.\nOptional machine-readable fields are included when the API can identify the semantic error,\nespecially for survey locale, identifier, and reference validation used by MCP and agent clients.\n","required":["name","reason"],"properties":{"name":{"type":"string","description":"Dot-path to the invalid request field.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].name","typeLabel":"string"},"reason":{"type":"string","description":"Human-readable repair guidance.","isRequired":true,"uniqueKey":"invalid_params.items.[INDEX].reason","typeLabel":"string"},"code":{"type":"string","enum":["dangling_reference","duplicate_identifier","duplicate_locale","forbidden_identifier","immutable_identifier","invalid_locale","invalid_reference","missing_required_field","misordered_reference","missing_translation","qsf_limit_exceeded","qsf_not_recognized","read_only_field","unsupported_field","unsupported_locale"],"description":"Machine-readable validation category, when available.\n\nThis is the shared error shape every v3 operation returns, so it lists what the API can emit today and nothing more — `INVALID_PARAM_CODES` in `app/api/v3/lib/response.ts` is the registry, and a code absent from it is dropped before the response is built. Categories for operations that do not exist yet are added with the code that emits them, not ahead of it.\n\n`qsf_not_recognized` and `qsf_limit_exceeded` come from the Qualtrics import (an internal, session-only route sharing this shape): the file is not a Qualtrics export it can read, or it is one past an import limit, which `identifier` names.\n","uniqueKey":"invalid_params.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"identifier":{"type":"string","description":"Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.","uniqueKey":"invalid_params.items.[INDEX].identifier","typeLabel":"string"},"referenceType":{"type":"string","enum":["block","element","ending","hiddenField","language","variable","variableName","recall"],"description":"Identifier namespace involved in the validation failure. Same rule as `code`: these are the namespaces the API emits today, and more arrive with the operations that emit them.","uniqueKey":"invalid_params.items.[INDEX].referenceType","typeLabel":"enum\u003cstring>"},"missingId":{"type":"string","description":"Referenced id that does not exist in the submitted survey document.","uniqueKey":"invalid_params.items.[INDEX].missingId","typeLabel":"string"},"firstUsedAt":{"type":"string","description":"Dot-path where a duplicate identifier was first used.","uniqueKey":"invalid_params.items.[INDEX].firstUsedAt","typeLabel":"string"},"conflictsWith":{"type":"string","description":"Dot-path to the existing identifier that conflicts with this field.","uniqueKey":"invalid_params.items.[INDEX].conflictsWith","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"invalid_params.items.[INDEX]","typeLabel":"object"},"uniqueKey":"invalid_params","typeLabel":"object[]"}},"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"title":"\u003cstring>","status":123,"detail":"\u003cstring>","requestId":"\u003cstring>","type":"\u003cstring>","instance":"\u003cstring>","code":"ai_features_not_enabled","details":{},"invalid_params":[{"name":"\u003cstring>","reason":"\u003cstring>","code":"dangling_reference","identifier":"\u003cstring>","referenceType":"block","missingId":"\u003cstring>","firstUsedAt":"\u003cstring>","conflictsWith":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"}},"security":{"98be720b-4c09-4f6c-af4f-6271c6b02080":{"sessionAuth":{"type":"apiKey","in":"cookie","name":"next-auth.session-token","description":"NextAuth session JWT cookie. **Development:** often `next-auth.session-token`.\n**Production (HTTPS):** often `__Secure-next-auth.session-token`. Send the cookie your browser receives after sign-in.\n","scopes":[],"schemeName":"sessionAuth"}},"db8f638c-cf5c-4177-9987-bb8d1ca1ffe2":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Management API key; must include **workspaceId** as an allowed workspace with read, write, or manage permission.\n","scopes":[],"schemeName":"apiKeyAuth"}},"175f5ced-7dd9-49de-8cfd-6f1252c9c53d":{"bearerAuth":{"type":"http","scheme":"bearer","description":"The same management API key, presented as `Authorization: Bearer \u003ckey>` instead of `x-api-key`. Equivalent in every way — same key, same workspace permissions — and offered because it is what most HTTP clients and SDK generators reach for by default.\nA **401** from any operation that accepts it carries `WWW-Authenticate: Bearer realm=\"formbricks\"`. Session-only operations accept neither this nor `x-api-key`.\n","scopes":[],"schemeName":"bearerAuth"}}},"servers":{"5a1d53ae-163d-4303-ac56-7a378a4d43bc":{"url":"https://app.formbricks.com","description":"Formbricks Cloud. Paths in this spec already include the `/api/v3` prefix; self-hosted instances substitute their own origin."}},"processedSecurityOptions":[{"schemes":[{"scheme":{"type":"apiKey","in":"cookie","name":"next-auth.session-token","description":"NextAuth session JWT cookie. **Development:** often `next-auth.session-token`.\n**Production (HTTPS):** often `__Secure-next-auth.session-token`. Send the cookie your browser receives after sign-in.\n","scopes":[],"schemeName":"sessionAuth"},"schema":{"title":"next-auth.session-token","type":"string","description":"NextAuth session JWT cookie. **Development:** often `next-auth.session-token`.\n**Production (HTTPS):** often `__Secure-next-auth.session-token`. Send the cookie your browser receives after sign-in.\n","isRequired":true,"typeLabel":"string","deprecated":false},"schemeName":"sessionAuth","name":"next-auth.session-token"}],"title":"sessionAuth"},{"schemes":[{"scheme":{"type":"apiKey","in":"header","name":"x-api-key","description":"Management API key; must include **workspaceId** as an allowed workspace with read, write, or manage permission.\n","scopes":[],"schemeName":"apiKeyAuth"},"schema":{"title":"x-api-key","type":"string","description":"Management API key; must include **workspaceId** as an allowed workspace with read, write, or manage permission.\n","isRequired":true,"typeLabel":"string","deprecated":false},"schemeName":"apiKeyAuth","name":"x-api-key"}],"title":"apiKeyAuth"},{"schemes":[{"scheme":{"type":"http","scheme":"bearer","description":"The same management API key, presented as `Authorization: Bearer \u003ckey>` instead of `x-api-key`. Equivalent in every way — same key, same workspace permissions — and offered because it is what most HTTP clients and SDK generators reach for by default.\nA **401** from any operation that accepts it carries `WWW-Authenticate: Bearer realm=\"formbricks\"`. Session-only operations accept neither this nor `x-api-key`.\n","scopes":[],"schemeName":"bearerAuth"},"schema":{"title":"Bearer Auth","type":"string","isRequired":true,"description":"The same management API key, presented as `Authorization: Bearer \u003ckey>` instead of `x-api-key`. Equivalent in every way — same key, same workspace permissions — and offered because it is what most HTTP clients and SDK generators reach for by default.\nA **401** from any operation that accepts it carries `WWW-Authenticate: Bearer realm=\"formbricks\"`. Session-only operations accept neither this nor `x-api-key`.\n","format":"bearer","typeLabel":"string\u003cbearer>","deprecated":false},"schemeName":"bearerAuth","name":"Authorization"}],"title":"bearerAuth"}]},"originalFileLocation":"api-v3-reference/openapi.yml"},"subdomain":"formbricks","siteName":"Documentation - Formbricks","title":"Update a response - Documentation - Formbricks","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-20T07:04:41.587Z","originalFileLocation":"api-v3-reference/v3-responses/update-a-response.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"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.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Documentation - Formbricks\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003cmeta data-mint-head name=\"robots\" content=\"noindex\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Update a response - Documentation - Formbricks\">\u003cmeta data-mint-head property=\"og:description\" content=\"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.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Documentation - Formbricks\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://formbricks.mintlify.site/docs/_mintlify/api/og/v2?division=V3+Responses\u0026amp;title=Update+a+response\u0026amp;description=Partially+updates+a+response.+Requires+read-write+access%3B+the+workspace+is+resolved+from+the+response.+An+unknown+or+inaccessible+%60responseId%60+answers+**403**.%0A\u0026amp;theme=5f66c2cdb60ad4d135e6b4e5\u0026amp;signature=354b655bfeb398310caf2d159c8c78804170a1d8f8670f38c5579c03289cd56e\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Update a response - Documentation - Formbricks\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"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.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://formbricks.mintlify.site/docs/_mintlify/api/og/v2?division=V3+Responses\u0026amp;title=Update+a+response\u0026amp;description=Partially+updates+a+response.+Requires+read-write+access%3B+the+workspace+is+resolved+from+the+response.+An+unknown+or+inaccessible+%60responseId%60+answers+**403**.%0A\u0026amp;theme=5f66c2cdb60ad4d135e6b4e5\u0026amp;signature=354b655bfeb398310caf2d159c8c78804170a1d8f8670f38c5579c03289cd56e\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/docs/mintlify-assets/_mintlify/favicons/formbricks/GlZ4IqkvkQlhFmAc/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Documentation - Formbricks\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#00C4B8\">\u003clink data-mint-head rel=\"canonical\" href=\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/docs/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/docs/api-v3-reference/v3-responses/update-a-response.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://formbricks.com/#organization\",\"name\":\"Documentation - Formbricks\",\"url\":\"https://formbricks.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/formbricks/wqEbm3FT1QK_6-vL/images/logo-light.svg?fit=max\u0026auto=format\u0026n=wqEbm3FT1QK_6-vL\u0026q=85\u0026s=365e5fb767b72b4fbbb2016860fd92ba\"}},{\"@type\":\"WebSite\",\"@id\":\"https://formbricks.com/docs#website\",\"name\":\"Documentation - Formbricks\",\"url\":\"https://formbricks.com/docs\",\"publisher\":{\"@id\":\"https://formbricks.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response#webpage\",\"url\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response\",\"name\":\"Update a response\",\"description\":\"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.\",\"dateModified\":\"2026-10-20T07:04:41.587Z\",\"isPartOf\":{\"@id\":\"https://formbricks.com/docs#website\"},\"breadcrumb\":{\"@id\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"V3 Responses\",\"item\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/list-responses\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Update a response\",\"item\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response\"}]},{\"@type\":[\"TechArticle\",\"APIReference\"],\"@id\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response#apireference\",\"headline\":\"Update a response\",\"name\":\"Update a response\",\"description\":\"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.\",\"url\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response\",\"mainEntityOfPage\":{\"@id\":\"https://formbricks.com/docs/api-v3-reference/v3-responses/update-a-response#webpage\"},\"dateModified\":\"2026-10-20T07:04:41.587Z\",\"publisher\":{\"@id\":\"https://formbricks.com/#organization\"},\"isPartOf\":{\"@id\":\"https://formbricks.com/docs#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Documentation - Formbricks\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Documentation - Formbricks","nav":{"global":null,"tabs":[{"tab":"Platform","pages":[{"title":"Platform","href":"/platform/introduction"}]},{"tab":"Surveys (Ask)","pages":[{"title":"Surveys (Ask)","href":"/surveys/overview"}]},{"tab":"Unify Feedback (Analyze)","pages":[{"title":"Unify Feedback (Analyze)","href":"/unify-feedback/overview"}]},{"tab":"Workflows (Act)","pages":[{"title":"Workflows (Act)","href":"/workflows/overview"}]},{"tab":"Self Hosting","pages":[{"title":"Self Hosting","href":"/self-hosting/overview"}]},{"tab":"Development","pages":[{"title":"Development","href":"/development/overview"}]},{"tab":"MCP","pages":[{"title":"MCP","href":"/platform/mcp/overview"}]},{"tab":"API v1 Reference","pages":[{"title":"API v1 Reference","href":"/api-reference/rest-api"}]},{"tab":"API v2 Reference (Beta)","pages":[{"title":"API v2 Reference (Beta)","href":"/api-v2-reference/introduction"}]},{"tab":"API v3 Reference (Private Beta)","hidden":true,"groups":[{"group":"API v3 Documentation (Private Beta)","pages":[{"title":"API v3 Reference (Private Beta)","description":"The next generation of the Formbricks management API, including the Feedback Records API","icon":"code-compare","href":"/api-v3-reference/introduction"}]},{"group":"V3 Surveys","pages":[{"title":"List surveys","description":"Returns the surveys in the workspace **that the caller may read**. A restricted survey is listed only\nto its owner and to the organisation's owners and managers; to everyone else, API keys included, it\ndoes not exist here. `meta.totalCount` and `meta.workspaceSurveyCount` count that same readable set,\nso neither number reveals a restricted survey to someone who cannot see it. The visibility predicate\nis applied in the query and the cursor, never after the fact: `limit` keeps its meaning and\n`meta.nextCursor` being `null` remains the only end-of-collection signal.\n\nSession cookie, `x-api-key`, or a Bearer token.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/surveys","href":"/api-v3-reference/v3-surveys/list-surveys"},{"title":"Create a survey","description":"Creates a block-based survey template from one strict survey document. The endpoint accepts\nmultilingual authoring maps keyed by real locale codes and converts them to Formbricks'\ninternal default-language representation. Non-default locale keys in translated content\nmust be declared in `languages`; undeclared locale keys return `unsupported_locale` in\n`invalid_params` instead of silently mutating workspace languages.\n\n`blocks[].id` and `variables[].id` are stable public identifiers. They may be omitted on\ncreate, in which case the server generates cuid2 ids. If the same create request needs to\nreference a block or variable from logic, provide explicit valid ids and use those references\nconsistently.\n\nFor normal sequential surveys, omit `logic` and `logicFallback`. `logicFallback` is only valid\nwhen the same block has at least one `logic` rule; otherwise the API returns\n`invalid_reference`.\n\n`type` may be omitted or set to `link` or `app`. App surveys additionally accept the `distribution` (display options + triggers) and `targeting` (contact segment filters) objects; both are rejected for link surveys. Trigger ids must reference existing workspace action classes (discover them via `GET /api/v3/action-classes`), and `targeting.filters` references (contact-attribute keys and segments) must exist in the workspace. Unsupported fields are rejected instead of ignored.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys","href":"/api-v3-reference/v3-surveys/create-a-survey"},{"title":"Create a survey draft payload with AI","description":"Creates a strict v3 `POST /api/v3/surveys` create payload from a creator prompt and returns\nvalidation metadata. This endpoint is single-purpose: it does **not** persist the survey.\nClients should validate the returned payload with `/api/v3/surveys/validate` or use the\nembedded validation metadata, then create the draft through `POST /api/v3/surveys`.\n\nThe generated payload always sets `status: draft`. Both `type: link` and `type: app` are\nsupported; for `app`, the payload includes a default `distribution` (display once, no triggers,\nno targeting) that you finish configuring in the editor or via the create request.\n\nPrompt privacy: prompt text is sent to the configured AI provider to create the payload, but\nthis endpoint does not store prompts and server logs should use request ids and failure\ncategories instead of raw prompt content.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys/generate","href":"/api-v3-reference/v3-surveys/create-a-survey-draft-payload-with-ai"},{"title":"Validate a survey document","description":"Validates a future create or patch survey payload without writing data, creating languages,\nor creating survey versions. Invalid survey documents return `200` with `data.valid=false`\nso agents and MCP clients can repair payloads from structured `invalid_params`.\n\nUse `400` only for malformed validation envelopes, unsupported operations, invalid `surveyId`,\nor unsupported query parameters.\n\n**Custom CSS.** A create or patch payload that carries `customCss` is also run through the custom CSS processor; the result adds the compiled `customCss` and `warnings`, or `errors` and `valid=false`. `operation: customCss` validates CSS alone for a live preview — with `scope: workspace | survey` and, optionally, an existing `surveyId` in `workspaceId` — and returns the compiled CSS and warnings. Validation never saves, versions, touches timestamps or invalidates caches, needs only read access, and grants nothing: the write itself checks permissions and the plan again. Requests that process custom CSS also count against a separate budget of 60 per minute per user or API key, shared with the workspace custom CSS endpoint, with survey creates and updates that add or edit custom CSS, and with the MCP tools; beyond it they return **429**.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys/validate","href":"/api-v3-reference/v3-surveys/validate-a-survey-document"},{"title":"Retrieve a survey","description":"Returns the public v3 survey management resource for one survey. By default, translatable\nfields are returned as stable multilingual maps keyed by the language codes emitted in\n`languages[].code`. Use `lang` to filter those maps to one or more requested language selectors.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/surveys/{surveyId}","href":"/api-v3-reference/v3-surveys/retrieve-a-survey"},{"title":"Delete a survey","description":"Deletes a survey by id. Session cookie, `x-api-key`, or a Bearer token.","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml delete /api/v3/surveys/{surveyId}","href":"/api-v3-reference/v3-surveys/delete-a-survey"},{"title":"Update a survey","description":"Updates a survey through a strict top-level partial document. Omitted top-level fields are\npreserved. Provided top-level objects and arrays replace that whole subtree; this endpoint\ndoes not deep-merge nested objects and does not implement JSON Patch.\n\nThis write endpoint accepts the canonical multilingual authoring document only. Translatable\nfields must be locale-code maps, not strings. `PATCH ?lang=...` and `?version=...` are not\nsupported in Scope 1.\n\nFor MCP and AI-agent workflows, fetch the current survey first, modify only the intended\ntop-level fields, call `/api/v3/surveys/validate` with `operation: patch` for preflight, then\nsubmit the same patch here. Missing ids in replaced arrays are treated as deletions. Variable\nname renames are allowed but may be destructive for downstream exports or integrations.\nElement id changes are rejected once the survey and element are no longer drafts.\n\nServer-owned fields that `GET` returns (`id`, `workspaceId`, `type`, `createdAt`, `archivedAt`, `defaultLanguage`, `languages[].alias`) are accepted and verified, so the output of a `GET` can be sent straight back. Echoing the value you were given is a no-op; changing one is a **422** `read_only_field`.\n\nA round-tripped `updatedAt` acts as an optimistic-concurrency precondition: the write is applied only if the survey has not changed since your read, and is otherwise rejected with **409**. Omit it and overlapping writes remain last-write-wins. Header-based preconditions (`ETag` / `If-Match`) are still future work.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/surveys/{surveyId}","href":"/api-v3-reference/v3-surveys/update-a-survey"},{"title":"Edit survey blocks","description":"Edits a survey's blocks in place — update, insert or remove whole blocks without resending the ones you are not changing. Operations apply in order against a single snapshot and are applied atomically: either all of them land or none do.\n\nBlocks are addressed by id, never by position, so a concurrent edit cannot silently redirect an operation onto a different block. An `update` replaces the target block entirely, the same whole-subtree rule `PATCH /api/v3/surveys/{surveyId}` uses, and the replacement must carry every language the survey has configured.\n\nPass `expectedUpdatedAt` (the `updatedAt` from your last read) to make the write conditional; see the **409** response. Session cookie or x-api-key.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/surveys/{surveyId}/blocks","href":"/api-v3-reference/v3-surveys/edit-survey-blocks"},{"title":"Set survey block order","description":"Replaces the order of a survey's blocks. The body lists every current block id exactly once, in the order you want; block content is untouched.\n\nIdempotent: the same `order` always yields the same survey, and an order that already matches performs no write at all, so `updatedAt` does not move and other callers' preconditions stay valid. A stale `expectedUpdatedAt` is still rejected with **409** on that path — the precondition is evaluated before the method, so a no-op outcome never reports success to a stale caller. Because the list must be a permutation, a dropped or duplicated block is rejected rather than silently applied — the failure mode that resending the whole `blocks` array cannot catch.\n\nPass `expectedUpdatedAt` (the `updatedAt` from your last read) to make the write conditional; see the **409** response. Session cookie or x-api-key.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml put /api/v3/surveys/{surveyId}/blocks/order","href":"/api-v3-reference/v3-surveys/set-survey-block-order"},{"title":"Archive a survey","description":"Archives (soft-deletes) a survey by id. Archived surveys are hidden from the default list, stop collecting responses, and become read-only until restored — a subsequent `PATCH` returns **422**. Works on any survey, including legacy question-based surveys, so this endpoint returns a minimal lifecycle acknowledgement rather than the full survey resource. Session cookie, `x-api-key`, or a Bearer token.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys/{surveyId}/archive","href":"/api-v3-reference/v3-surveys/archive-a-survey"},{"title":"Restore a survey","description":"Restores a previously archived (soft-deleted) survey by id, clearing its archive marker so it reappears in the default list and becomes editable again. Works on any survey, including legacy question-based surveys, so this endpoint returns a minimal lifecycle acknowledgement rather than the full survey resource. Session cookie, `x-api-key`, or a Bearer token.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys/{surveyId}/restore","href":"/api-v3-reference/v3-surveys/restore-a-survey"},{"title":"Read a survey's visibility and the impact of changing it","description":"Returns the survey's enforced visibility, its owner, the caller's access, and what a change would do: `blockers[]` lists the outbound connections that currently make `restricted` refusable (webhooks, integrations, active feedback sources, and draft or enabled workflows), `impact` carries the numbers the confirmation dialogues show, `allowedTargets[]` the values a `POST` would act on right now, and `pending` / `version` describe a change still in flight. Read this before offering \"Make restricted\"; the `POST` answers **409** while `blockers[]` is non-empty.\n\nRequires `survey.change_visibility`, the same permission as the `POST`: the survey's owner while they still hold read access to the workspace, or an organisation owner/manager, with the feature enabled. Everyone else — read or write members, team-level managers, every API key, and anyone who cannot see the survey at all — answers **403** with the same body as an unknown id. Connection names and ids are configuration, not survey content, and only the people who may act on them get to read them.\n\nWhile `pending` is non-null the survey is restricted for everyone but its owner (within their workspace access: read to view, write to edit) and the organisation's owners and managers, whichever direction is queued — the application fails closed until the graph acknowledges the change — so `visibility` reads `restricted` in that state and `allowedTargets` includes the value that cancels the pending change.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/surveys/{surveyId}/visibility","href":"/api-v3-reference/v3-surveys/read-a-surveys-visibility-and-the-impact-of-changing-it"},{"title":"Change a survey's visibility","description":"Sets the survey to `restricted` or `workspace`.\n\n**Restrictions take effect at once.** A `workspace → restricted` request stores the flag, a new `version` and a pending marker in one transaction. From that instant the survey is restricted on every path — single reads, lists and counts, responses, exports — for everyone but the owner and the organisation's owners and managers, whether or not the graph has caught up (fail closed). The owner keeps only the access their workspace role gives them — read to view, write to edit; an owner who loses workspace access loses the survey too. The relationships are then projected; the answer is **200** either way, with `pending: null` when the graph acknowledged this `version` inside the request and `pending: \"restricted\"` when the outbox will finish it.\n\n**Grants take effect when enforced.** A `restricted → workspace` request answers **200** only once the graph has acknowledged this exact `version`; until then the survey stays restricted. If the in-request projection fails, the answer is **503** `projection_pending`: the change is stored and queued, `GET …/visibility` shows `pending: \"workspace\"`, and retrying is safe.\n\n**Fencing.** Every accepted change increments the survey's `version`. Projection work carries the version it was issued for, and a projector holding an older version never writes, so a worker leased before a newer change cannot overtake it — including one that crashed before its verification pass. A **200** for a grant means the graph holds this exact version.\n\n**No-ops and cancellation.** Requesting the value the survey already enforces, with nothing pending, is a **200** no-op: nothing is written or audited and `changedAt` / `changedBy` are unchanged. Requesting the enforced value while the *opposite* is pending **cancels** the pending change: a new version is stored, the queued work is superseded, and the answer is **200** with `pending: null`. Requesting the pending value again re-attempts the projection (200 or 503).\n\nWhen `restricted` is refused for two reasons at once — no owner **and** outbound connections — the **422** wins: the missing owner cannot be fixed, so listing connections to remove would send the caller down a dead end.\n\nAllowed for the survey's owner (while they still hold read access to the workspace) and for the organisation's owners and managers. **Not** for workspace members with write or team-level manage rights, and never for API keys. Every successful change is audit-logged with the actor.\n\nGoing `workspace → restricted` is refused with **409** while any outbound connection depends on the survey; resolve them first. It is refused with **422** when the survey has no owner, because \"restricted\" would then mean \"administrators only\" — duplicate the survey for a restricted copy. Going `restricted → workspace` has no preconditions.\n\nThis is the only way to change visibility: `PATCH /api/v3/surveys/{surveyId}` accepts `visibility`, `owner` and `access` only as the exact values `GET` returned (so a fetched survey round-trips), and rejects any other value as an unsupported field. Publishing does not change visibility; the publish dialogue chains this call before the status change.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/surveys/{surveyId}/visibility","href":"/api-v3-reference/v3-surveys/change-a-surveys-visibility"}]},{"group":"V3 Responses","pages":[{"title":"List responses","description":"Returns responses for a workspace, newest first. Session cookie, `x-api-key`, or a Bearer token, with read access.\n\n**Scoping.** `workspaceId` is required, as on every v3 collection read. Add `surveyId` to narrow to one survey — the common case, and the one the storage layer is indexed for (`(surveyId, createdAt)`). A workspace-wide list has to reach responses through their surveys and is correspondingly slower, so pass `surveyId` whenever you know it. `contactId` narrows to one respondent.\n\n**Pagination is keyset**, on `(createdAt, id)`. The `cursor` is opaque and bound to the sort order and the filter set it was issued for; changing either invalidates it. `meta.nextCursor` being `null` is the only end-of-collection signal — do not infer the end from a short page.\n\n`id` is there purely as a tie-breaker, and it makes the order **total**, so no row is ever skipped or repeated across pages. What it is not is a finer-grained clock: `createdAt` is stored to millisecond precision, so responses sharing a millisecond — everything written in one bulk insert, for instance — come back in an order that is stable and repeatable but is not their true creation order. Do not read the sequence within a millisecond as meaningful.\n\n**Filters are allow-listed**, and each one on its own is either served by an index or bounded by one. `filter[finished][eq]` and `filter[language][in]` additionally require `surveyId` or `contactId`, because both are heap residuals that are only bounded when the scan is anchored to one survey or one contact; sending either without such a scope returns **400**.\n\nBe aware that **combining** a `filter[createdAt]` bound with `filter[finished][eq]` is the one case no single index covers: the ordered scan uses `(surveyId, createdAt)` and `finished` stays a heap residual, so a page whose matches are sparse reads far more rows than it returns. Prefer narrowing by `surveyId` and a tight `createdAt` window, and treat that combination as the slow path until a covering index exists.\n\n**What this endpoint deliberately does not filter on:** individual answers, hidden-field values, submission metadata (`source`, `url`, `country`, `browser`, `os`, `deviceType`), contact attributes, quotas, and tags. Each is a JSON path or an unindexed join on the largest table in the database, where the honest outcome is a timeout rather than a slow query. The in-app dashboard does offer these, so this endpoint is knowingly behind it; closing the gap needs index work and is tracked separately rather than quietly omitted.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/responses","href":"/api-v3-reference/v3-responses/list-responses"},{"title":"Create a response","description":"Records a survey response. Requires read-write access to the workspace, which is resolved from `surveyId` rather than supplied by the caller.\n\n**This performs a real submission.** It runs the full response pipeline — webhooks, integrations, follow-ups, quota evaluation, and Hub ingestion once the response is finished — and it consumes one metered monthly response. That is the same power a v1/v2 management create has today, and the same power an API key with write access already carries; it is called out because an agent holding that key can create genuine submissions. Use `POST /api/v3/responses/validate` to check a payload without any of that happening.\n\n`createdAt` cannot be supplied. Bulk historical loading is therefore not what this endpoint is for — see `ResponseListItem.createdAt` for why, and use v2 while a dedicated import path is outstanding.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/responses","href":"/api-v3-reference/v3-responses/create-a-response"},{"title":"Count responses","description":"Returns how many responses match a scope and filter set, without fetching any of them.\n\nThis exists as its own operation because \"how many match this?\" is the question asked most often and it should not require paging through data to answer — particularly for agent clients, where a page of payloads to obtain a single integer is pure waste. It takes the same scope and filter parameters as `GET /api/v3/responses`, minus pagination and sorting, so a count and a page are always answering the same question.\n\nThe list endpoint's `meta.totalCount` covers the other case: a caller already paging that also wants the total. That one is opt-in; this one is the direct route.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/responses/count","href":"/api-v3-reference/v3-responses/count-responses"},{"title":"Validate a response payload","description":"Checks a create or patch payload and reports what a real write would do — **without writing anything**. Never has side effects: no response is created, no pipeline fires, nothing is metered.\n\nDeliberately the same shape as `POST /api/v3/surveys/validate`: same dual auth, a request discriminated on `operation` carrying the body the real call would take, and a result of `valid`, `operation` and `invalid_params` with document-relative paths. The value is that the two endpoints behave alike, so a client can validate uniformly. Answers are checked against the survey's own validation rules, so a payload this endpoint accepts is one the real write accepts.\n\n**It reports effects, not just a verdict.** When `valid` is `true`, `effects` names the language that would be stamped, the contact and display that would be linked, the quotas that would count, whether the pipeline would fire, and whether the write would be metered. The language is the field that motivated this endpoint: an importer can see that a payload produces language-less responses *before* writing thousands of them.\n\nAuthorization is the same as the operation being validated — read-write for `create` and `patch` — so this cannot be used as a cheaper cross-tenant existence probe than the write itself. An inaccessible `responseId` on a patch validation answers **403**, as the patch would.\n\n**What it does not do:** an endpoint lets a caller check; it cannot stop a caller writing a bad record anyway. Consistency across the write paths comes from all of them calling the same validator — this endpoint is that validator's public face, not a gate in front of it.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/responses/validate","href":"/api-v3-reference/v3-responses/validate-a-response-payload"},{"title":"Delete responses in bulk","description":"Deletes up to 100 responses in one transaction. Requires `manage` access to the workspace.\n\n**A POST custom method, not a `DELETE` on the collection.** A collection-level `DELETE` that loses its filters — through a client bug, a proxy stripping a query string, a copied curl command — degrades into erasing every response in scope. A distinct path cannot be reached by accident. AIP-165 blesses `batchDelete` for exactly this reason.\n\n**It scope-filters rather than rejecting.** Ids belonging to another workspace, and ids that no longer exist, are ignored; the ids in the caller's own workspace are deleted and `deleted` reports how many that was. Rejecting the whole call on a foreign id would do two bad things at once: leak that the id exists somewhere, and break retries — after a partially-applied call, an already-deleted id is indistinguishable from a foreign one. Scope-filtering is idempotent and leaks nothing, so a client can safely retry the same batch.\n\nBecause `deleted` can therefore be lower than the number of ids sent — or zero — do not treat a shortfall as an error. Deletion cascades to uploaded files exactly as the single-response delete does — and, exactly as it does, leaves the Hub copy in place. See that operation for why.\n\n**`workspaceId` is the scope, and it is required.** Unlike the single-response delete — which resolves the workspace *from* the response so a caller cannot pair someone else's id with a scope it happens to hold — a batch has no single response to resolve from, and scope-filtering is only meaningful against a known workspace. Supplying it is safe here because the same value is both what `manage` is checked against and what the delete is filtered by: the two cannot diverge, so a foreign id matches nothing rather than being deleted under a scope the caller does hold.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/responses/batch-delete","href":"/api-v3-reference/v3-responses/delete-responses-in-bulk"},{"title":"Get a response","description":"Returns one response in the detailed view — everything the list view carries, plus `contact`, `displayId`, `singleUseId`, and the stored answer map `data`.\n\nThe workspace is resolved from the response itself, so the caller cannot choose which scope it is authorized against and no `workspaceId` parameter is accepted. An unknown or inaccessible `responseId` answers **403**, not **404**, so response ids cannot be probed for existence.\n\n**Round-tripping.** This read returns `data` in the stored, write-shaped form, so the simplest correction is to take it, change one entry and `PATCH` it back. Embedded Data round-trips through `embeddedData[]` instead: the `key` you read is the `key` a write accepts. `answers[]` is the typed view of the same information, offered alongside. Independently of that, a client working from a *list* row — which omits the maps — can still rebuild the write-shaped payload: `answers[]`, `embeddedData[]` and `unresolved[]` together carry every stored key and value **verbatim**, with no need to refetch the survey. Two rules make that guarantee hold, and both are contractual:\n\n`rawKey`, `rawValue`, `valueText`, `valueDate`, `fileUrls` and composite slot values are always the stored bytes, never resolved or reformatted — the resolved forms live in the separate `elementLabel`, `optionId`, `optionLabel`, `rowId` and `columnId` fields. And **empty-but-present values are returned, not omitted**: the `\"\"` of an unchecked consent, the `\"\"` of a selected but unfilled Other, a blank composite slot. That second rule matters because `PATCH` replaces `data` wholesale, so an omitted key is a deleted answer rather than an unchanged one.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/responses/{responseId}","href":"/api-v3-reference/v3-responses/get-a-response"},{"title":"Delete a response","description":"Permanently deletes a response. Requires `manage` access; the workspace is resolved from the response, and an unknown or inaccessible `responseId` answers **403**.\n\nDeletion cascades to any files uploaded with the response, which are removed from storage after the row is committed. No `responseDeleted` webhook is emitted — no such trigger exists.\n\n**The Hub copy is not removed.** A submission forwarded to the Hub stays there and stays searchable through the feedback-record endpoints after this returns 204. That cascade is ENG-2855 and lands after the Hub release; until then, an erasure workflow has to delete the feedback record itself. Stated because a caller cannot cheaply verify deletion semantics on a destructive endpoint.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml delete /api/v3/responses/{responseId}","href":"/api-v3-reference/v3-responses/delete-a-response"},{"title":"Update a response","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/responses/{responseId}","href":"/api-v3-reference/v3-responses/update-a-response"}]},{"group":"V3 Workflows","pages":[{"title":"List workflows","description":"Returns workflow list items for a workspace, sorted by `sortBy` (default `updatedAt`,\nnewest first). Workflows are workspace-scoped and exposed through the same v3\nsession-or-API-key authorization model as surveys.\n\nList items are intentionally slim: they include the derived `triggerType` and `surveyId`\nplus a `lastRun` summary, but not the full `definition`. Use\n`GET /api/v3/workflows/{workflowId}` to read the definition.\n\n`archived` workflows are excluded unless explicitly requested with\n`filter[status][in]=archived`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/workflows","href":"/api-v3-reference/v3-workflows/list-workflows"},{"title":"Create a workflow","description":"Creates a `draft` workflow. The workflow definition is the public source of truth used by\nthe dashboard and external clients, following the document-first pattern established by the\nv3 Survey API. Runtime persistence, revision creation, and version snapshotting are\nimplementation details behind this contract.\n\nWorkflows are always created as drafts; use `POST /api/v3/workflows/{workflowId}/enable` to\nmake them respond to trigger events. Graph reference failures (duplicate node ids, edges\npointing at missing nodes, `entryNodeId` not referencing the trigger, unknown `surveyId` or\n`endingCardIds` in the workspace) are rejected with **400** and structured `invalid_params`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows","href":"/api-v3-reference/v3-workflows/create-a-workflow"},{"title":"List workflow runs","description":"Returns workflow run summaries for a workspace, newest first (`createdAt` descending). Use\n`workflowId` to narrow the list to one workflow, or `responseId` to find the runs a specific\nsurvey response produced.\n\nList items are summaries without `triggerPayload`, `data`, or `logs`; fetch\n`GET /api/v3/workflows/runs/{runId}` for the full debug-oriented payload. This keeps\nresponse-level data out of list reads by default.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/workflows/runs","href":"/api-v3-reference/v3-workflows/list-workflow-runs"},{"title":"Retrieve a workflow run","description":"Returns one workflow run by globally unique run id, including the full debug payload: the\ncaptured `triggerPayload`, the run `data` (per-step results), the persisted `logs` trace,\nand retry metadata (`attempt`, `nextAttemptAt`, `lastErrorAt`). Scope 1 deliberately exposes\nthese as JSON dumps so users and agents can understand exactly what happened in a run.\n\nPoll this endpoint after `POST /api/v3/workflows/{workflowId}/test`: runs execute\nasynchronously and transition `queued` → `running` → `completed`/`failed`/`canceled`.\n\nUnknown or inaccessible run ids return `403` rather than `404` to avoid leaking\ncross-workspace resource existence.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/workflows/runs/{runId}","href":"/api-v3-reference/v3-workflows/retrieve-a-workflow-run"},{"title":"Retrieve a workflow","description":"Returns one workflow definition by id. Unknown or inaccessible workflow ids return `403`\nrather than `404` to avoid leaking cross-workspace resource existence.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/workflows/{workflowId}","href":"/api-v3-reference/v3-workflows/retrieve-a-workflow"},{"title":"Delete a workflow","description":"Permanently deletes a workflow by id. Deletion cascades to the workflow's versions, runs,\nand run logs so no response-derived data is left behind (GDPR requirement). This is\nirreversible; use `POST /api/v3/workflows/{workflowId}/archive` for a reversible\nsoft-delete that keeps history. Returns `204 No Content`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml delete /api/v3/workflows/{workflowId}","href":"/api-v3-reference/v3-workflows/delete-a-workflow"},{"title":"Update a workflow","description":"Updates human-facing workflow metadata and/or the workflow definition. Status changes are\nhandled by lifecycle endpoints (`enable`, `disable`, `archive`, `unarchive`) so the state\nmachine remains explicit. Provided fields replace the corresponding stored fields; this\nendpoint does not implement JSON Patch.\n\n`name` and `description` can be updated in any state except `archived`. The `definition`\ncan only be updated while the workflow is `draft` or `disabled`: the live version of an\n`enabled` workflow is an immutable snapshot, so definition changes require disabling (or\nmoving to draft) first and re-enabling afterwards. Violations return **422** with code\n`invalid_workflow_state`. A structurally invalid definition returns **400** with structured `invalid_params`; the referenced survey and ending cards are not verified here — that existence check runs when the workflow is enabled.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/workflows/{workflowId}","href":"/api-v3-reference/v3-workflows/update-a-workflow"},{"title":"Duplicate a workflow","description":"Creates a new `draft` workflow by copying an existing workflow's definition, name (or the\nprovided override), and description. The duplicate receives its own id and timestamps and\nstarts with empty run and version history; the source workflow is unchanged. Archived\nworkflows can be duplicated (the copy starts as a normal draft).\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/duplicate","href":"/api-v3-reference/v3-workflows/duplicate-a-workflow"},{"title":"Enable a workflow","description":"Moves a workflow from `draft` or `disabled` to `enabled`. Only enabled workflows respond to\ntrigger events.\n\nEnabling validates that the definition is executable — the trigger has exactly one outgoing\nedge (trigger-only drafts cannot be enabled), the graph is acyclic, every node is\nreachable from the trigger, only Scope 1 node types are present, the referenced survey\nand ending cards still exist, and every `send_email` action addressing a *literal* email address\ntargets someone who can access this workspace (an element-id `to` resolves to the respondent's own\naddress and is exempt) — and then snapshots an immutable workflow version. Runs\nreference that snapshot via `workflowVersionId`, so later draft edits never change what an\nenabled workflow executes. Validation failures return **422** with code\n`workflow_not_executable` and structured `invalid_params`; calling enable in a state other\nthan `draft` or `disabled` returns **422** with code `invalid_workflow_state`. Two concurrent enable requests race to publish the next version; the loser is rejected with **409** code `conflict` and can safely retry.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/enable","href":"/api-v3-reference/v3-workflows/enable-a-workflow"},{"title":"Disable a workflow","description":"Moves an `enabled` workflow to `disabled`. Disabled workflows keep their configuration but\ndo not respond to trigger events; runs that are already queued or running are not canceled.\nCalling disable in any other state returns **422** with code `invalid_workflow_state`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/disable","href":"/api-v3-reference/v3-workflows/disable-a-workflow"},{"title":"Archive a workflow","description":"Soft-deletes a workflow by moving it from any non-archived state to `archived`. Archived\nworkflows stop responding to trigger events immediately, become read-only, and are excluded\nfrom list responses unless requested with `filter[status][in]=archived`. Their runs, version\nhistory, and definition are preserved, and the workflow can be restored with\n`POST /api/v3/workflows/{workflowId}/unarchive`. Archiving an already archived workflow\nreturns **422** with code `invalid_workflow_state`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/archive","href":"/api-v3-reference/v3-workflows/archive-a-workflow"},{"title":"Unarchive a workflow","description":"Restores an `archived` workflow to `draft`. Unarchiving never re-enables a workflow\nautomatically: re-enable explicitly with `POST /api/v3/workflows/{workflowId}/enable`, which\nre-validates the definition (the referenced survey or ending cards may have changed while\nthe workflow was archived). Calling unarchive on a non-archived workflow returns **422**\nwith code `invalid_workflow_state`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/unarchive","href":"/api-v3-reference/v3-workflows/unarchive-a-workflow"},{"title":"Dry-run (validate) a workflow","description":"Validates a workflow without running it or causing any side effect. It checks that the workflow's current definition is executable, that the trigger's referenced survey and ending cards still exist in the workspace, and that every `send_email` action addressing a *literal* email address targets someone who can access this workspace. **No** workflow run is created and **no** email or webhook is sent.\n\nThe response is always `200` with `{ data: { workflowId, ok, problems } }`. `data.ok` is `true` when the workflow would execute; otherwise `data.problems` lists every issue found — each with a machine-readable `code` and the offending `field` — so they can be fixed in a single pass.\n\nDrafts are testable — validating the setup before going live is the point of a dry run. Only an `archived` workflow is rejected, with **422** and code `invalid_workflow_state`.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/test","href":"/api-v3-reference/v3-workflows/dry-run-validate-a-workflow"}]},{"group":"V3 Action Classes","pages":[{"title":"List action classes","description":"Returns the action classes (user actions that can trigger an app survey) for a workspace. Use\nthe returned ids as `distribution.triggers[].actionClassId` when creating or updating app\nsurveys. Read-only: this v3 endpoint lists action classes but does not create them — action\nclasses are managed in the Formbricks app (Actions settings) or via the v1/v2 management API.\n\nAn empty `data` array means the workspace has no action classes yet. Triggers are optional, so\nyou can still create an app survey with `distribution.triggers: []` (or omit `distribution`\nentirely) and attach triggers later via `PATCH` once an action class exists. Session cookie, `x-api-key`, or a Bearer token.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/action-classes","href":"/api-v3-reference/v3-action-classes/list-action-classes"}]},{"group":"V3 Contact Attribute Keys","pages":[{"title":"List contact attribute keys","description":"Returns the contact attribute keys for a workspace. Use the returned `key` values as\n`targeting.filters[].root.contactAttributeKey` when targeting app surveys; `dataType` hints which\noperators apply. Session cookie, `x-api-key`, or a Bearer token. (Applying targeting filters requires the Contacts\nentitlement; this discovery endpoint does not.)\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/contact-attribute-keys","href":"/api-v3-reference/v3-contact-attribute-keys/list-contact-attribute-keys"}]},{"group":"V3 Workspaces","pages":[{"title":"Read workspace custom CSS","description":"Returns the workspace's shared survey CSS as editable **source** (light and dark), the one recoverable previous revision, whether the stored CSS is being delivered to respondents (`status`), and whether this caller may change it (`canEdit`, `planAllowed`). Compiled output is never returned here. Requires read access to the workspace. Session cookie, `x-api-key`, or a Bearer token.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/workspaces/{workspaceId}/custom-css","href":"/api-v3-reference/v3-workspaces/read-workspace-custom-css"},{"title":"Save or clear workspace custom CSS","description":"Saves or clears the workspace's shared survey CSS. `customCss` replaces both fields; `null` clears all of it. The source is processed by the same processor every save path uses: constructs it removes come back as top-level `warnings`, and CSS it rejects is a **422** that leaves the saved CSS in effect. The replaced value becomes `previous`, the one recoverable revision — restore it by sending its source back. Unchanged source writes nothing.\n\nWorkspace CSS reaches every survey in the workspace, so writing it is narrower than writing a survey: **organization owners and managers** for signed-in users (session or OAuth), and a **`manage`** grant on this workspace for API keys — a Read \u0026 write grant is refused with **403**. On Formbricks Cloud, adding or editing needs the Scale plan (**403** `custom_css_plan_required`); clearing never does. Successful changes refresh the cached configuration the SDKs receive (they pick it up on their next configuration fetch). Saves that add or edit CSS count against a custom CSS budget of 60 per minute per user or API key, shared with custom CSS validation and with survey writes that add or edit custom CSS; beyond it they return **429**. Re-saving the same CSS and clearing it are never counted.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/workspaces/{workspaceId}/custom-css","href":"/api-v3-reference/v3-workspaces/save-or-clear-workspace-custom-css"}]},{"group":"V3 Feedback Records","pages":[{"title":"List feedback records","description":"Lists the feedback records of one dataset, newest first, with optional filters and keyset\npagination. Served by the API gateway, which authorizes the call against `tenant_id` and\nforwards it to the feedback store — see the introduction for how the two layers answer.\n\nRequires an API key with **read** permission (or higher) on a workspace the dataset is assigned\nto. The organization must hold the Unify Feedback entitlement.\n\nMulti-value filters are OR-ed within a parameter and AND-ed across parameters. Repeat the\nparameter to pass several values (`?field_type=text\u0026field_type=rating`); comma-separated values\nare **not** split. Filters on enrichment members (`sentiment`, `emotions`, `sentiment_score_*`)\nnever match records that have not been enriched — use `has_sentiment=false` / `has_emotions=false`\nto find those.\n\n**Incremental extraction.** To keep a copy of a dataset in sync, filter on `updated_since` and\npage with `sort=updated_at\u0026order=asc`, bounded by `updated_until`: every page is then one index\nrange, and a record that changes while you page normally comes back later in the run. Start each\nrun with a `limit=1` request and read its `Date` header: that value is this run's `updated_until`\nand, minus a few minutes, the next run's `updated_since` — both bounds from the same value, or\nrecords between them are never read. Do not use the newest `updated_at` received instead: it\nskips a write that was still committing when you read past its timestamp.\nUpsert by `id` and let the most recent fetch win. Deleted records leave nothing for\n`updated_since` to match, so reconcile deletions separately: periodically list every record id\nwith no `updated_since`, and remove the local records that were not returned and were created\nbefore that listing's start (its first response's `Date` header) minus the overlap. Run one\nsync at a time.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/feedbackRecords","href":"/api-v3-reference/v3-feedback-records/list-feedback-records"},{"title":"Create a feedback record","description":"Stores one feedback record in the dataset named by `tenant_id` in the body. The gateway reads\n`tenant_id` from the JSON body to authorize the call — a body that is not JSON, or has no\n`tenant_id`, answers **400** before anything is stored.\n\nRequires an API key with **write** permission (or higher) on a workspace the dataset is\nassigned to. Creating is allowed on shared datasets (it is ordinary workspace work, like a CSV\nimport); only changing and deleting existing records is restricted to unshared ones.\n\nIngestion is idempotent per (`tenant_id`, `submission_id`, `field_id`): a second record with the\nsame triple answers **409** rather than creating a duplicate. Send exactly one `value_*` member,\nmatching `field_type`. A `text` record is enriched asynchronously — `sentiment`, `emotions` and\nthe translation members appear on the record once enrichment completes, and it becomes\nsearchable once embedded.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/feedbackRecords","href":"/api-v3-reference/v3-feedback-records/create-a-feedback-record"},{"title":"Delete a user's feedback records","description":"Permanently deletes every record in the dataset whose `user_id` matches — the erasure operation\nfor GDPR Article 17 requests. Derived embeddings are removed with the records. Idempotent:\nrepeating the call answers `deleted_count: 0`.\n\n`tenant_id` is **required**: the gateway scopes erasure to one dataset. (The underlying store\ncan erase a user across every dataset when `tenant_id` is omitted; that form is not reachable\nthrough this API.) Records written without a `user_id` cannot be reached this way — delete them\nindividually.\n\nDeletion is unrecoverable, so it requires an API key with **manage** permission on a workspace\nthe dataset is assigned to, and the dataset must be assigned to **exactly one** workspace: on a\nshared dataset no workspace permission can say whose records these are, so API-key mutations\nare refused with **403**.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml delete /api/v3/feedbackRecords","href":"/api-v3-reference/v3-feedback-records/delete-a-users-feedback-records"},{"title":"Search feedback records semantically","description":"Embeds `query` and returns the ids of the records in the dataset whose text is closest to it,\nwith cosine similarity scores. Only records with a non-empty `value_text` are embedded, so only\nthose can match. The gateway reads `tenant_id` from the JSON body to authorize the call.\nRequires an API key with **read** permission (or higher) on a workspace the dataset is\nassigned to.\n\nResults carry ids and the embedded text only — fetch a match with\n`GET /api/v3/feedbackRecords/{id}` for the full record. Answers **503** on instances without\nan embedding model configured.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml post /api/v3/feedbackRecords/search/semantic","href":"/api-v3-reference/v3-feedback-records/search-feedback-records-semantically"},{"title":"Get a feedback record","description":"Returns one feedback record by id, with its read-only assignment in the active directory\ntaxonomy. The assignment reflects the current visible path in the active run; editing a record\ndoes not reclassify it until another run. `taxonomy` is null if its lookup is unavailable.\nThe gateway looks the record up to learn its dataset and\nauthorizes against that, so no `tenant_id` is sent. Requires an API key with **read**\npermission (or higher) on a workspace the record's dataset is assigned to.\n\nAn unknown id, or one in a dataset you cannot read, answers **403** rather than 404, so ids\ncannot be probed. The exception is the minute after a delete: the gateway still has the\nrecord's dataset cached, authorizes the call, and the store answers **404**.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/feedbackRecords/{id}","href":"/api-v3-reference/v3-feedback-records/get-a-feedback-record"},{"title":"Delete a feedback record","description":"Permanently deletes one record and its derived embedding. Unrecoverable, so it requires an API\nkey with **manage** permission on a workspace the record's dataset is assigned to, and the\ndataset must be assigned to **exactly one** workspace: on a shared dataset no workspace\npermission can say whose record this is, so API-key mutations are refused with **403**.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml delete /api/v3/feedbackRecords/{id}","href":"/api-v3-reference/v3-feedback-records/delete-a-feedback-record"},{"title":"Update a feedback record","description":"Changes the answer, `language`, `user_id` or `metadata` of one record. Only the members sent\nare changed; provenance (`tenant_id`, `source_*`, `field_*`, `submission_id`, `collected_at`)\nis immutable. Requires an API key with **write** permission (or higher) on a workspace the\nrecord's dataset is assigned to, and the dataset must be assigned to **exactly one** workspace\n— see the delete operation for why.\n\nTwo behaviours are silent and worth knowing: `metadata` is **replaced**, not merged; and\nchanging `value_text` clears the enrichment members (`sentiment`, `sentiment_score`,\n`emotions`, `value_text_translated`, `translation_lang_key`) and queues re-enrichment and\nre-embedding — the response shows them absent until that completes. Changing `language` clears\nand re-queues the translation pair only.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml patch /api/v3/feedbackRecords/{id}","href":"/api-v3-reference/v3-feedback-records/update-a-feedback-record"},{"title":"Find similar feedback records","description":"Returns the records in the same dataset whose embedded text is closest to the given record's,\nwith cosine similarity scores. The anchor record itself is excluded. The gateway looks the\nrecord up to learn its dataset and authorizes against that. Requires an API key with **read**\npermission (or higher) on a workspace the record's dataset is assigned to.\n\nAnswers **403** for an unknown or inaccessible id. Answers **404** when the record exists but has\nno embedding — it has no text, or was created moments ago and is not embedded yet. Answers\n**503** on instances without an embedding model configured.\n","deprecated":null,"version":null,"openapi":"/api-v3-reference/openapi.yml get /api/v3/feedbackRecords/{id}/similar","href":"/api-v3-reference/v3-feedback-records/find-similar-feedback-records"}]}]}]},"basePath":"","title":"Update a response - Documentation - Formbricks","buildId":"dpl_BSPUBxEoKhhK7HzXVDPxdPvWx4xc","deploymentId":"dpl_BSPUBxEoKhhK7HzXVDPxdPvWx4xc","dynamics":["c2aa737661","2b790eda92","b0f84f53ff","a9bb46feb3"],"chunks":["OpenApiReferenceProvider-Do6EtGSl.js","utils-id4tQ6My.js","cloneDeep-izs3Hzxb.js","_Uint8Array-BVIqJ90l.js","isObjectLike-C8vmzPEN.js","isArray-Dcu1dQ5t.js","utils-Byrixac4.js","toString-BX-iyh8G.js","isSymbol-CSH9U2Gm.js","addUniqueKeysToSchema-nakYa3Zl.js","snippetPresets-Bq-pUS7h.js","style-to-object-BY3JEAdk.js","v4-DIgz8zy6.js","conditional-requirements-CFM0id0Q.js","ApiExamples-CujTC_9K.js","isEqual-BfaiBbcl.js","usePlaygroundInputsStore-FneVUsVu.js","shallow-DY9WpUCz.js","getApiPlaygroundDisplay-Bz8WpPfA.js","CodeSnippet-DgCuJg4G.js","CodeSnippets-Dub9c4T0.js","Page-CePXnjd1.js","getPastedJson-xdI8y2XB.js","icon--yVw5vkr.js","MethodPill-C47t7MdS.js","DuplicateIcon-BzwJ2LeC.js","createIcon-BzggGy84.js","DynamicMarkdown-AlxJK8VR.js","SectionHeading-CMlwFxdK.js","axios-vUo_bRtp.js","Markdown-BNgNrPsi.js","clsx-apD-Q0Oo.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","html-void-elements-mCSPHezU.js","dist-qmLwQyWY.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js","AssistantPageLink-Dc4qTkWx.js"]}