---
title: "Update a feedback record"
method: PATCH
path: "/api/v3/feedbackRecords/{id}"
tags: ["V3 Feedback Records"]
---

# Update a feedback record

`PATCH /api/v3/feedbackRecords/{id}`

## Request

- Base URL: `https://app.formbricks.com`
- URL: `https://app.formbricks.com/api/v3/feedbackRecords/{id}`
- Auth: API key in header `x-api-key`

Changes the answer, `language`, `user_id` or `metadata` of one record. Only the members sent
are changed; provenance (`tenant_id`, `source_*`, `field_*`, `submission_id`, `collected_at`)
is immutable. Requires an API key with **write** permission (or higher) on a workspace the
record's dataset is assigned to, and the dataset must be assigned to **exactly one** workspace
— see the delete operation for why.

Two behaviours are silent and worth knowing: `metadata` is **replaced**, not merged; and
changing `value_text` clears the enrichment members (`sentiment`, `sentiment_score`,
`emotions`, `value_text_translated`, `translation_lang_key`) and queues re-enrichment and
re-embedding — the response shows them absent until that completes. Changing `language` clears
and re-queues the translation pair only.

## Path parameters

- `id` string, uuid, required

## Request body

- UpdateFeedbackRecordRequest: A partial update. Only the members sent are changed; an empty body is accepted and changes nothing. Provenance (`tenant_id`, `source_*`, `field_*`, `submission_id`, `collected_at`) is immutable.
  - `value_text` string: New text answer. Changing it clears the server-generated enrichment members (`sentiment`, `sentiment_score`, `emotions`, `value_text_translated`, `translation_lang_key`), queues re-enrichment and re-embedding, and the response reflects the cleared state.
  - `value_id` string: New stable option id for a choice answer. Opaque, not validated.
  - `value_number` number, double: New numeric answer. Must be between -1e15 and +1e15.
  - `value_boolean` boolean: New yes / no answer.
  - `value_date` string, date-time: New date answer. Must be between 1970-01-01 and 2080-12-31.
  - `language` string: New ISO language code. Changing it clears and re-queues the translation pair only.
  - `user_id` string: New end-user identifier.
  - `metadata` object, nullable: **Replaces** the stored metadata wholesale — it is not merged, so send every key you want to keep. Same value rules and **400** rejections as on create.

## Response `200`

Feedback record updated

- FeedbackRecord: One piece of feedback in the normalized Feedback Record shape. Field names are `snake_case`: the payload is the Hub's, passed through the API gateway unchanged. Optional members are **absent** (not `null`) when unset, except `taxonomy` on the get-one response, which is null if its lookup fails.
  - `taxonomy` FeedbackRecordTaxonomy: Read-time view of the record's membership in the active directory taxonomy.
    - `status` 'classified' | 'unclassified' | 'no_active_taxonomy', required
    - `run_id` string, uuid, nullable, required
    - `path` object[], required: Currently visible non-root nodes in root-to-leaf order; empty unless classified.
      - `id` string, uuid, required
      - `label` string, required
      - `level` integer, required
      - `node_type` 'branch' | 'leaf', required
  - `id` string, uuid, required: UUIDv7 primary key.
  - `tenant_id` string, cuid2, required: The feedback dataset this record belongs to (the dataset id, as shown by the MCP `list_feedback_datasets` tool).
  - `collected_at` string, date-time, required: When the feedback was given.
  - `created_at` string, date-time, required: When the record was stored. Diverges from `collected_at` on a historical re-import.
  - `updated_at` string, date-time, required: When the record was last changed.
  - `source_type` string, required: Type of feedback source, e.g. `survey`, `review`, `feedback_form`.
  - `source_id` string: Reference to the survey / form / ticket the record came from.
  - `source_name` string: Human-readable name of the source.
  - `submission_id` string, required: Identifier of the logical submission this record belongs to (e.g. a response id). Records are unique per (`tenant_id`, `submission_id`, `field_id`).
  - `field_id` string, required: Identifier of the question / field.
  - `field_label` string: The question text.
  - `field_group_id` string: Stable identifier grouping related fields (ranking, matrix and grid questions).
  - `field_group_label` string: Human-readable question text for the group.
  - `field_type` 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date', required: The type of a feedback field, which determines which `value_*` member carries the answer: `text` → `value_text` (the only enrichable type), `categorical` → `value_text` + `value_id`, `nps` / `csat` / `ces` / `rating` / `number` → `value_number`, `boolean` → `value_boolean`, `date` → `value_date`.
  - `language` string: ISO language code the feedback was given in.
  - `user_id` string: End-user identifier (e.g. an anonymous id or an email hash).
  - `value_text` string: Text answer.
  - `value_id` string: Stable id of the selected option in the source system (e.g. a survey choice id), stored alongside `value_text` so choice answers keep their identity across label edits and languages. Opaque. Absent for free-text and non-choice answers.
  - `value_number` number, double: Numeric answer (ratings, NPS scores, numbers).
  - `value_boolean` boolean: Yes / no answer.
  - `value_date` string, date-time: Date answer.
  - `metadata` union: Arbitrary context stored with the record and returned with equivalent values (key order and number formatting are normalized). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the write operations accept; the wider union exists so records written before that convention still deserialize.
    - object
    - unknown[]
      - unknown
    - string
    - number
    - boolean
  - `sentiment` 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed': Sentiment polarity label inferred from `value_text` by the sentiment enrichment: five ordinal levels plus a distinct `mixed`. Server-generated and read-only.
  - `sentiment_score` number, double: Signed sentiment polarity from `-1.0` (very negative) to `1.0` (very positive). Read-only; absent until the record is enriched.
  - `emotions` FeedbackRecordEmotion[]: Emotions inferred from `value_text`; multi-label from a fixed set. Read-only, and **never an empty array** — the column is either absent or has at least one label. Absent covers two different states: not yet classified, and classified with no emotion detected. Do not read absence as "not enriched yet"; `has_emotions=false` selects both, and the enrichment-status endpoint is what distinguishes them.
  - `value_text_translated` string: `value_text` translated into the dataset's configured target language. Read-only; absent until the record is enriched.
  - `translation_lang_key` string: BCP-47 locale that `value_text_translated` was produced in. Read-only; absent until the record is enriched.

## Other responses

- `400`: Bad Request. From the **gateway** (`text/plain`): a missing or malformed `tenant_id`; a request body that is not a single JSON object; a body whose top-level keys can be read more than one way (a repeated key, a key with non-ASCII characters, or a key that differs from `tenant_id` only in letter case); a `tenant_id` query parameter given more than once; or a path under `/api/v3/feedbackRecords` that is not one of the documented operations. A record id that is not a UUID and a path such as `/count` both fall in that last case and share the one `Unsupported FeedbackRecords route` body, so the two cannot be told apart from the response. From the **feedback store** (`application/problem+json`): schema validation failures — an unknown enum value, an inverted range, a malformed or re-ordered cursor, or a string with NULL bytes or invalid UTF-8.
- `401`: Unauthorized — no credentials, or an API key that does not exist or is revoked. Answered by the gateway as `text/plain`, before the request reaches the feedback store.
- `403`: Forbidden. Answered by the gateway as `text/plain` whenever the request cannot be authorized: the dataset does not exist, is archived, or is not assigned to a workspace the API key has the required permission on; the organization lacks the Unify Feedback entitlement; a mutation was attempted with an API key on a dataset shared by more than one workspace; or the record id does not exist. Existence is not leaked: an unknown dataset or record is refused here rather than with a 404. The single-record operations are the one exception, and only for a minute after a delete — see their `404`.
- `404`: Not Found from the feedback store (`application/problem+json`, `code: not_found`). The record was deleted after the gateway resolved its dataset: that lookup is cached for 60 seconds, so for up to a minute after a delete the call is still authorized and reaches the store, which no longer has the record. Once the cache entry expires the same id answers **403** instead. Treat both as "this record is not available to you".
- `409`: Conflict (`application/problem+json`, `code: tenant_write_conflict`). The dataset is being **purged** while you write to it. Transient: retry the request unchanged once the purge completes. Only creates can conflict on a duplicate, so this operation never answers `code: conflict`.
- `413`: Payload Too Large. The gateway buffers the request body in order to authorize it, and the proxy in front of it stops reading at 1 MiB — Envoy's `maxRequestBytes` on Formbricks Cloud, Traefik's `maxbodysize` in the Docker install. The refusal is therefore generated by that proxy rather than by the application, so neither its media type nor its body is part of this contract; branch on the status alone.
- `500`: Internal Server Error from the feedback store (`application/problem+json`, `code: internal_server_error`).
- `503`: Service Unavailable. From the **gateway** (`text/plain`): the record's dataset could not be resolved because the feedback store did not answer, the authorization service itself was unreachable, or the record's stored `tenant_id` is not a dataset id this API can address. From the **feedback store** (`application/problem+json`, `code: service_unavailable`): the operation needs a capability that is not configured on this instance — semantic search and similar records require an embedding model.

## Changes

- **2026-10-01** `f917fab96782`: 1 info
  - added the optional property `taxonomy` to the response with the `200` status
- **2026-09-09** `b127aa22f81c`: 1 info
  - endpoint added

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

---

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