---
title: "Validate a response payload"
method: POST
path: "/api/v3/responses/validate"
tags: ["V3 Responses"]
---

# Validate a response payload

`POST /api/v3/responses/validate`

## Request

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

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.

Deliberately 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.

**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.

Authorization 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.

**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.

## Request body

- union
  - ValidateResponseCreateRequest
    - `operation` 'create', required
    - `data` unknown, required
  - ValidateResponsePatchRequest
    - `operation` 'patch', required
    - `responseId` string, cuid2, required
    - `data` unknown, required

## Response `200`

Validation completed. A `200` means the check ran, **not** that the payload is valid — read `valid`.

- object
  - `data` ResponseValidationResult, required: Outcome of a dry-run validation. Deliberately the same shape as `SurveyValidationResult` — the value of this endpoint is that the two behave alike, so a client can treat validation uniformly. Answers are checked against the survey's own validation rules (`SurveyValidationRule`) rather than a parallel rule set, so a payload this endpoint accepts is a payload the real write accepts.
    - `valid` boolean, required: Whether the submitted payload would be accepted by the real operation.
    - `operation` 'create' | 'patch', required
    - `invalid_params` InvalidParam[], required: Repairable validation errors, using paths relative to the submitted `data` document.
      - `name` string, required: Dot-path to the invalid request field.
      - `reason` string, required: Human-readable repair guidance.
      - `code` 'dangling_reference' | 'duplicate_identifier' | 'duplicate_locale' | 'forbidden_identifier' | 'immutable_identifier' | 'invalid_locale' | 'invalid_reference' | 'limit_exceeded' | 'missing_required_field' | 'misordered_reference' | 'missing_translation' | 'qsf_limit_exceeded' | 'qsf_not_recognized' | 'read_only_field' | 'unsupported_field' | 'unsupported_locale': Machine-readable validation category, when available. This 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. `limit_exceeded`: the field is well formed but past a limit the operation states, which `reason` names together with what to change. Chart queries use it for a date range that spans more time buckets than its granularity allows. `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.
      - `identifier` string: Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code.
      - `referenceType` 'block' | 'element' | 'ending' | 'hiddenField' | 'language' | 'variable' | 'variableName' | 'recall': 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.
      - `missingId` string: Referenced id that does not exist in the submitted survey document.
      - `firstUsedAt` string: Dot-path where a duplicate identifier was first used.
      - `conflictsWith` string: Dot-path to the existing identifier that conflicts with this field.
    - `effects` ResponseValidationEffects: What a successful write **would** do, reported without doing it. This mirrors the detail that makes `POST /api/v3/surveys/validate` useful: it reports effects, not just a verdict, so a caller can see the consequences of a payload before committing thousands of them. Present only when `valid` is `true`.
      - `language` string, nullable, required: The survey language code that would be stamped on the response. `null` means the response would be stored **without** a language — the case worth catching before a bulk write, since language-less responses cannot later be told apart by language.
      - `contactId` string, nullable: The contact that would be linked, or `null` if the response would be anonymous.
      - `displayId` string, nullable: The display that would be linked, or `null` if none.
      - `firesPipeline` boolean, required: Whether a real write would run the response pipeline — webhooks, integrations, follow-ups, and Hub ingestion. Always `true`: a create emits `responseCreated` and every patch emits `responseUpdated`, so a write is never side-effect free. A patch that transitions the response to finished additionally emits `responseFinished`; the transition decides which events go out, not whether any do.
      - `countsTowardMeteredResponses` boolean, required: Whether a real write would consume one metered monthly response.
      - `quotas` object[]: Quotas this response would count against, and whether it would fill them. Empty when the survey defines no quotas or the plan does not include them.
        - `quotaId` string, cuid2, required
        - `quotaName` string, required
        - `wouldCount` boolean, required: Whether this response matches the quota's criteria.
        - `wouldFill` boolean: Whether counting this response would take the quota to its limit. Absent when `wouldCount` is `false`.
      - `tagsToApply` string[]: Tag ids that would end up applied to the response. Patch operations only.

## Other responses

- `400`: Bad Request — the envelope itself is malformed: missing or unknown `operation`, missing `data`, or a `patch` without `responseId`; or the body breaks the request limits (an array past its budget, or nesting past 256 levels). Other problems *inside* `data` are the point of this endpoint and come back as `200` with `valid: false`, not as a 400.
- `401`: Not authenticated (no valid session or API key).
- `403`: Forbidden — no workspace access, or resource does not exist (404 not used; avoids existence leak).
- `429`: Rate limit exceeded.
- `500`: Internal Server Error.

## Changes

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

[Operation history](https://skmtc.dev/formbricks/apis/formbricks-api-v3/changes/api/v3/responses/validate/post.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)
