# GENERATED FILE — do not edit. Source of truth: docs/api-v3-reference/src/ (one file per path/schema). # Regenerate with `pnpm api:v3:bundle`; CI verifies freshness with `pnpm api:v3:check`. # V3 API — Surveys and Workflows extension (hand-maintained source; not produced by generate-api-specs). openapi: 3.1.1 info: title: Formbricks API v3 description: | **Surveys**: **GET /api/v3/surveys**, **POST /api/v3/surveys**, **POST /api/v3/surveys/generate**, **POST /api/v3/surveys/validate**, **GET /api/v3/surveys/{surveyId}**, **PATCH /api/v3/surveys/{surveyId}**, and **DELETE /api/v3/surveys/{surveyId}**. **Workflows extension**: **GET /api/v3/workflows**, **POST /api/v3/workflows**, **GET /api/v3/workflows/{workflowId}**, **PATCH /api/v3/workflows/{workflowId}**, **DELETE /api/v3/workflows/{workflowId}**, **POST /api/v3/workflows/{workflowId}/duplicate**, **POST /api/v3/workflows/{workflowId}/enable**, **POST /api/v3/workflows/{workflowId}/disable**, **POST /api/v3/workflows/{workflowId}/archive**, **POST /api/v3/workflows/{workflowId}/unarchive**, **POST /api/v3/workflows/{workflowId}/test**, **GET /api/v3/workflows/runs**, and **GET /api/v3/workflows/runs/{runId}**. Authenticate all management endpoints with **session cookie** or **`x-api-key`** (management key with access to the workspace). **Spec location:** source of truth is the multi-file tree at `docs/api-v3-reference/src/` (root `openapi.yml` plus one file per path and component); `docs/api-v3-reference/openapi.yml` is the generated single-file bundle (alongside v2 at `docs/api-v2-reference/openapi.yml`). **workspaceId** `workspaceId` is the canonical container identifier for this API. It is a **required query parameter on collection read endpoints** (`GET /api/v3/surveys`, `GET /api/v3/action-classes`, `GET /api/v3/contact-attribute-keys`) because those operate within a workspace. **`POST /api/v3/surveys` instead requires `workspaceId` in the request body**, not as a query parameter. Single-resource endpoints (`GET`/`PATCH`/`DELETE /api/v3/surveys/{surveyId}`) locate the resource by its globally-unique id and resolve the workspace from it, so they do **not** accept a `workspaceId` query parameter — sending one returns **400** (`Unrecognized key: "workspaceId"`). **Auth** Authenticate with either a session cookie or **`x-api-key`**. In dual-auth mode, V3 checks the API key first when the header is present, otherwise it uses the session path. Unauthenticated callers get **401** before query validation. **Pagination** Cursor-based pagination with **limit** + opaque **cursor** token. Responses return `meta.nextCursor`; pass that value back as `cursor` to fetch the next page. Survey list responses also include `meta.totalCount`, the total number of surveys matching the current filters across all pages. Workflow list responses do not require total counts in Scope 1. There is no `offset` in this contract. **Filtering** Filters use explicit operator-style query parameters under the **`filter[...]` family**. The survey list supports `filter[name][contains]`, `filter[status][in]`, and `filter[type][in]`. The workflow list supports `filter[name][contains]` and `filter[status][in]`; the workflow run list supports `filter[status][in]` and `filter[isDryRun][eq]`. Resource-scoping identifiers (`workspaceId`, `workflowId`, `responseId`) stay plain query parameters. Multi-value filters use repeated keys or comma-separated values (e.g. `filter[status][in]=draft&filter[status][in]=inProgress` or `filter[status][in]=draft,inProgress`). Sorting remains a flat `sortBy` query parameter. **Security** Missing/forbidden workspace returns **403** with a generic message (not **404**) so resource existence is not leaked. Workflow and workflow-run detail endpoints also return **403** for unknown IDs or inaccessible IDs to avoid IDOR leaks. List responses use `private, no-store`. **AI survey creation** `POST /api/v3/surveys/generate` returns a draft `POST /api/v3/surveys` payload plus validation metadata. It does not create a survey. It generates survey content for both `link` and `app` surveys; for `app` it seeds a default `distribution` (display once, no triggers, no targeting) that you finish configuring before publishing. Prompt text is sent to the configured AI provider for generation, but it is not stored by this endpoint, not logged by default, and not included in audit data. **OpenAPI** This spec is **not** produced by `pnpm generate-api-specs` (that script only builds v2 → `docs/api-v2-reference/openapi.yml`). When the route contract changes, edit the files under `docs/api-v3-reference/src/`, then run `pnpm api:v3:bundle` to regenerate the committed single-file bundle. `pnpm api:v3:lint` lints the source tree; `pnpm api:v3:check` (CI) fails when the bundle is stale. **Workflows Scope 1** Workflows are a follow-up extension of the existing v3 Survey API work. The Survey API establishes API-first survey authoring; Workflows build on that same v3 style to automate actions around survey responses through workspace-scoped JSON definitions. Scope 1 models the existing Follow-ups capability as workflows: one `response.completed` trigger with an optional `endingCardIds` filter, and `send_email` actions with Follow-up email field parity. The definition and run shapes mirror the shared Zod schemas in `packages/workflows` (ENG-1100), which are the implementation source of truth for this contract. Workflows use `draft`, `enabled`, and `disabled` lifecycle states plus an `archived` soft-delete state. Status only changes through the lifecycle endpoints (`enable`, `disable`, `archive`, `unarchive`); it is not writable via `POST` or `PATCH`. Enabling validates that the definition is executable and snapshots an immutable workflow version; runs reference that snapshot via `workflowVersionId`. `POST .../test` is a dry run: it validates the definition and resolves the trigger's references synchronously, creating no run and sending no email, and returns `{ workflowId, ok, problems }` directly. Real runs execute asynchronously and are polled via `GET /api/v3/workflows/runs/{runId}`. **Overview migration note** The v3-backed survey overview page intentionally removes actions that are not yet exposed by this contract: `Created by` filtering, `Duplicate`, `Copy...`, `Preview`, and `Copy link`. **Next steps (out of scope for this spec)** Additional v3 survey update endpoints, optional ETag/304, field selection, translation-specific writes, and survey version history. version: 0.1.0 x-implementation-notes: route: apps/web/app/api/v3/surveys/route.ts generate-route: apps/web/app/api/v3/surveys/generate/route.ts query-parser: apps/web/app/api/v3/surveys/parse-v3-surveys-list-query.ts auth: apps/web/app/api/v3/lib/auth.ts workspace-resolution: apps/web/app/api/v3/lib/workspace-context.ts workflow-contract-ticket: ENG-1101 workflow-shared-schemas: packages/workflows/src/types (feat/workflows-types) workflow-prisma-schema: packages/database/schema/workflows.prisma (feat/workflows-prisma-schema) openapi-generated: false spec-source: docs/api-v3-reference/src (regenerate bundle with `pnpm api:v3:bundle`) pagination-model: cursor cursor-pagination: supported servers: - 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. paths: /api/v3/surveys: get: operationId: getSurveysV3 summary: List surveys description: | Returns surveys for the workspace. Session cookie or x-api-key. tags: - V3 Surveys parameters: - in: query name: workspaceId required: true schema: type: string format: cuid2 description: | Workspace identifier. This is the canonical container ID for v3 APIs. - in: query name: limit schema: type: integer minimum: 1 maximum: 100 default: 20 description: Page size (max 100) - in: query name: cursor schema: type: string description: | Opaque cursor returned as `meta.nextCursor` from the previous page. Omit on the first request. - in: query name: includeTotalCount schema: type: boolean default: true description: | Whether to calculate `meta.totalCount` for this request. Set to `false` on cursor-pagination follow-up requests to skip the extra count query; in that case `meta.totalCount` is `null`. - in: query name: filter[name][contains] schema: type: string maxLength: 512 description: Case-insensitive substring match on survey name (same as in-app list filters). - in: query name: filter[status][in] schema: type: array items: type: string enum: - draft - inProgress - paused - completed - archived style: form explode: true description: | Survey status filter. Repeat the parameter (`filter[status][in]=draft&filter[status][in]=inProgress`) or use comma-separated values (`filter[status][in]=draft,inProgress`). Invalid values → **400**. `archived` is a pseudo-status: it does not match a real survey status but includes archived (soft-deleted) surveys in the result set. Archived surveys are otherwise excluded from the default list. - in: query name: filter[type][in] schema: type: array items: type: string enum: - link - app style: form explode: true description: Survey type filter (`link` / `app`). Same repeat-or-comma rules as `filter[status][in]`. - in: query name: sortBy schema: type: string enum: - createdAt - updatedAt - name - relevance description: Sort order. Defaults to `updatedAt`. The `cursor` token is bound to the selected sort order. responses: '200': description: Surveys retrieved 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 - meta properties: data: type: array items: $ref: '#/components/schemas/SurveyListItem' meta: type: object required: - limit - nextCursor - totalCount - hasArchived properties: limit: type: integer nextCursor: type: - string - 'null' description: Opaque cursor for the next page. `null` when there are no more results. totalCount: type: - integer - 'null' minimum: 0 description: Total number of surveys matching the current filters across all pages. `null` when `includeTotalCount=false`. hasArchived: type: - boolean - 'null' description: '`true` when the workspace has at least one archived (soft-deleted) survey. Computed only on the first page (same gate as `totalCount`); `null` when `includeTotalCount=false`.' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] post: operationId: createSurveyV3 summary: Create a survey description: | Creates a block-based survey template from one strict survey document. The endpoint accepts multilingual authoring maps keyed by real locale codes and converts them to Formbricks' internal default-language representation. Non-default locale keys in translated content must be declared in `languages`; undeclared locale keys return `unsupported_locale` in `invalid_params` instead of silently mutating workspace languages. `blocks[].id` and `variables[].id` are stable public identifiers. They may be omitted on create, in which case the server generates cuid2 ids. If the same create request needs to reference a block or variable from logic, provide explicit valid ids and use those references consistently. For normal sequential surveys, omit `logic` and `logicFallback`. `logicFallback` is only valid when the same block has at least one `logic` rule; otherwise the API returns `invalid_reference`. `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. tags: - V3 Surveys requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSurveyRequest' examples: productFeedback: summary: Create a multilingual block-based survey value: workspaceId: clxx1234567890123456789012 name: Product Feedback Survey status: draft defaultLanguage: en-US metadata: cx_operation: enterprise_onboarding title: en-US: Product Feedback de-DE: Produktfeedback languages: - code: de-DE enabled: true welcomeCard: enabled: true headline: en-US: Welcome de-DE: Willkommen blocks: - name: Main Block elements: - id: satisfaction type: openText headline: en-US: What should we improve? de-DE: Was sollen wir verbessern? required: true endings: [] hiddenFields: enabled: false variables: [] appSurvey: summary: Create an in-app (app) survey with distribution and triggers description: | App surveys are shown inside your web or mobile app. `distribution` controls display behavior and the action classes that trigger the survey (by id — discover ids via `GET /api/v3/action-classes`). `targeting.filters` scopes the audience; an empty array targets everyone. `type` is immutable after creation. value: workspaceId: clxx1234567890123456789012 name: In-App Onboarding Feedback type: app status: draft defaultLanguage: en-US blocks: - name: Onboarding elements: - id: onboarding_feedback type: openText headline: en-US: How was your setup experience? required: false endings: [] hiddenFields: enabled: false variables: [] distribution: displayOption: respondMultiple recontactDays: 7 delay: 5 triggers: - actionClassId: clyy1234567890123456789012 targeting: filters: - id: clf0aaaaaaaaaaaaaaaaaaaa01 connector: null resource: id: clf1aaaaaaaaaaaaaaaaaaaa01 root: type: attribute contactAttributeKey: plan qualifier: operator: equals value: pro - id: clf2aaaaaaaaaaaaaaaaaaaa01 connector: and resource: - id: clf3aaaaaaaaaaaaaaaaaaaa01 connector: null resource: id: clf4aaaaaaaaaaaaaaaaaaaa01 root: type: attribute contactAttributeKey: role qualifier: operator: equals value: admin - id: clf5aaaaaaaaaaaaaaaaaaaa01 connector: or resource: id: clf6aaaaaaaaaaaaaaaaaaaa01 root: type: attribute contactAttributeKey: role qualifier: operator: equals value: owner sequentialProductSurvey: summary: Create a richer sequential survey without logicFallback description: | Multi-block survey with generated block and variable ids. Sequential surveys should omit `logic` and `logicFallback`; blocks are shown in array order. value: workspaceId: clxx1234567890123456789012 name: Sequential Product Survey defaultLanguage: en-US languages: - code: de-DE enabled: true metadata: cx_operation: product_feedback title: en-US: Sequential Product Survey de-DE: Sequenzielle Produktumfrage welcomeCard: enabled: true headline: en-US: Help us improve the product de-DE: Hilf uns, das Produkt zu verbessern hiddenFields: enabled: true fieldIds: - account_id - plan variables: - name: feedback_score type: number value: 0 blocks: - name: Satisfaction elements: - id: overall_rating type: rating headline: en-US: How satisfied are you with the product? de-DE: Wie zufrieden bist du mit dem Produkt? required: true range: 5 scale: smiley - id: product_area type: multipleChoiceSingle headline: en-US: Which area should we improve first? de-DE: Welchen Bereich sollten wir zuerst verbessern? required: true choices: - id: onboarding label: en-US: Onboarding de-DE: Onboarding - id: analytics label: en-US: Analytics de-DE: Analysen - name: Feedback elements: - id: free_text_feedback type: openText headline: en-US: What is one thing we should change? de-DE: Was sollten wir ändern? required: false longAnswer: true endings: - id: cmpsequentialend0000000000 type: endScreen headline: en-US: Thanks for your feedback! de-DE: Danke für dein Feedback! responses: '201': description: Survey created successfully headers: Location: schema: type: string description: Relative URL of the created survey resource. example: /api/v3/surveys/clsv1234567890123456789012 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: $ref: '#/components/schemas/SurveyResource' '400': description: | Bad Request — the document failed schema validation, i.e. any rule checkable from the request body alone, without consulting stored state. Covers: invalid JSON; unknown or unsupported fields (including `distribution`/`targeting` sent on a `link` survey, which are `app`-only); missing required fields; wrong types or out-of-range values; bad enum values; malformed multilingual maps; and intra-document field-combination rules — notably `displayLimit` is required (must be >= 1) when `displayOption` is `displaySome`. Cross-reference failures that need stored state to detect return **422** instead. Every offending field is itemized in `invalid_params`. Unknown or forbidden fields carry `code: unsupported_field` (e.g. `distribution` on a link survey); omissions carry `code: missing_required_field` (e.g. `displayLimit` when `displayOption` is `displaySome`). Bare type, range, enum, and malformed-locale-map violations report the field `name` with a human-readable `reason` and no machine `code`. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '401': $ref: '#/components/responses/V3Unauthorized' '403': description: Forbidden — no write access, missing external URL permission, missing Contacts entitlement for app-survey targeting, or workspace does not exist (404 not used; avoids existence leak) content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '422': description: 'Unprocessable Content — the document passed schema validation but failed a cross-reference check that requires stored state to detect: duplicate stable ids, dangling logic/jump references, undeclared locale keys used in content, invalid media URLs, a `distribution.triggers[].actionClassId` referencing an action class that does not exist in the workspace (discover valid ids via `GET /api/v3/action-classes`), or a `targeting.filters` entry that cannot resolve in the workspace — an unknown contact-attribute key or segment, an unsupported person identifier, or an unknown device value. The `invalid_params` array pinpoints each issue (e.g. `code: invalid_reference` with the offending `identifier`).' content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/surveys/generate: post: operationId: generateSurveyV3 summary: Create a survey draft payload with AI description: | Creates a strict v3 `POST /api/v3/surveys` create payload from a creator prompt and returns validation metadata. This endpoint is single-purpose: it does **not** persist the survey. Clients should validate the returned payload with `/api/v3/surveys/validate` or use the embedded validation metadata, then create the draft through `POST /api/v3/surveys`. The generated payload always sets `status: draft`. Both `type: link` and `type: app` are supported; for `app`, the payload includes a default `distribution` (display once, no triggers, no targeting) that you finish configuring in the editor or via the create request. Prompt privacy: prompt text is sent to the configured AI provider to create the payload, but this endpoint does not store prompts and server logs should use request ids and failure categories instead of raw prompt content. tags: - V3 Surveys requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GenerateSurveyRequest' examples: onboarding: summary: Create a link survey draft value: workspaceId: clxx1234567890123456789012 type: link language: en-US prompt: Understand why new users stop during onboarding and what would help them finish setup. responses: '200': description: Survey create payload generated successfully. The survey has not been created yet. 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: $ref: '#/components/schemas/GenerateSurveyResult' examples: generated: summary: Generated draft create payload value: data: language: en-US payload: workspaceId: clxx1234567890123456789012 type: link name: Onboarding Completion Survey status: draft defaultLanguage: en-US languages: - code: en-US default: true enabled: true metadata: title: en-US: Onboarding Completion Survey welcomeCard: enabled: false blocks: - id: clbk1234567890123456789012 name: Main elements: - id: q_1_example type: openText headline: en-US: What made onboarding hard to complete? required: false isDraft: true inputType: text longAnswer: false charLimit: enabled: false endings: - id: clen1234567890123456789012 type: endScreen headline: en-US: Thanks for your feedback hiddenFields: enabled: false variables: [] validation: valid: true invalid_params: [] languages: - code: en-US default: true enabled: true '400': description: Malformed body or a prompt that is too short or underspecified content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: shortPrompt: summary: Prompt needs more detail value: title: Bad Request status: 400 detail: Prompt needs more detail code: bad_request requestId: req_123 invalid_params: - name: prompt reason: Describe the survey goal, audience, or topic in a sentence so the AI can create a useful draft. '401': $ref: '#/components/responses/V3Unauthorized' '403': description: Forbidden — no write access, workspace does not exist, AI is not in plan, or AI smart tools are disabled content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: noWorkspaceWriteAccess: summary: No workspace write access value: title: Forbidden status: 403 detail: You are not authorized to access this resource code: forbidden requestId: req_123 aiDisabled: summary: AI smart tools disabled value: title: AI Unavailable status: 403 detail: AI smart tools are disabled for this organization. code: ai_smart_tools_disabled requestId: req_123 aiNotInPlan: summary: AI features not enabled for the plan value: title: AI Unavailable status: 403 detail: AI features are not enabled for this organization's plan. code: ai_features_not_enabled requestId: req_123 '422': description: AI generated an invalid payload after schema and v3 create validation, or the generation stopped because the output token limit was reached content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: invalidGeneratedPayload: summary: Invalid generated payload value: title: Unprocessable Content status: 422 detail: 'Generated survey payload is invalid: generatedSurvey.blocks: Too small' code: ai_generated_payload_invalid requestId: req_123 invalid_params: - name: generatedSurvey.blocks reason: Too small aiOutputTooLong: summary: Output token limit reached value: title: Unprocessable Content status: 422 detail: The generated survey exceeded the AI output token limit. Simplify the prompt or split it into smaller surveys. code: ai_output_too_long requestId: req_123 '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' '502': description: The configured AI provider failed or could not produce a valid draft content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: providerFailed: summary: AI provider failed value: title: Bad Gateway status: 502 detail: The AI provider could not generate a valid survey draft. Try again or add more detail. code: bad_gateway requestId: req_123 '503': description: AI is not configured on this Formbricks instance content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: instanceNotConfigured: summary: AI instance configuration missing value: title: AI Unavailable status: 503 detail: AI is not configured for this Formbricks instance. code: ai_instance_not_configured requestId: req_123 security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/surveys/validate: post: operationId: validateSurveyV3 summary: Validate a survey document description: | Validates a future create or patch survey payload without writing data, creating languages, or creating survey versions. Invalid survey documents return `200` with `data.valid=false` so agents and MCP clients can repair payloads from structured `invalid_params`. Use `400` only for malformed validation envelopes, unsupported operations, invalid `surveyId`, or unsupported query parameters. tags: - V3 Surveys requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ValidateSurveyRequest' examples: createValidation: summary: Validate a create payload value: operation: create data: workspaceId: clxx1234567890123456789012 name: Product Feedback Survey defaultLanguage: en-US blocks: - id: clbk1234567890123456789012 name: Main Block elements: - id: satisfaction type: openText headline: en-US: What should we improve? required: true patchValidation: summary: Validate a patch payload value: operation: patch surveyId: clsv1234567890123456789012 data: blocks: - id: clbk1234567890123456789012 name: Main Block elements: - id: satisfaction type: openText headline: en-US: What should we improve? required: true responses: '200': description: Validation completed. The survey document may still be invalid. 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: $ref: '#/components/schemas/SurveyValidationResult' examples: invalid: summary: Invalid survey document value: data: valid: false operation: create invalid_params: - name: blocks.0.elements.0.buttonUrl reason: Unsupported field 'buttonUrl' for element type 'openText'. Allowed fields... valid: summary: Valid survey document value: data: valid: true operation: create invalid_params: [] languages: - code: en-US default: true enabled: true writeBehavior: connect_or_create '400': description: Malformed validation envelope, invalid route/query data, or unsupported operation content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/surveys/{surveyId}: get: operationId: getSurveyV3 summary: Retrieve a survey description: | Returns the public v3 survey management resource for one survey. By default, translatable fields are returned as stable multilingual maps keyed by the language codes emitted in `languages[].code`. Use `lang` to filter those maps to one or more requested language selectors. tags: - V3 Surveys parameters: - in: path name: surveyId required: true schema: type: string format: cuid2 description: Survey identifier. - in: query name: lang required: false style: form explode: false schema: type: array items: type: string examples: - - de-DE - - de-DE - pt-PT - - de - - zh-Hans - - zh-Hans-CN description: | Comma-separated language selector filter for translatable fields, for example `?lang=de-DE,pt-PT`. The response shape stays stable: translatable fields are always maps, never strings, and response keys match the emitted `languages[].code` values for this survey. For compatibility with existing Formbricks surveys, GET accepts language tags such as `de`, `de-DE`, `zh-Hans`, and `zh-Hans-CN`, accepts `_` or `-` separators, is case-insensitive, and accepts configured workspace aliases such as `english`. Bare language selectors are resolved against the survey's configured languages and return `400` if ambiguous, for example if both `en-US` and `en-GB` are configured. Disabled-but-configured languages are readable in the management API so unfinished translations can be completed. responses: '200': description: Survey retrieved 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: $ref: '#/components/schemas/SurveyResource' examples: multilingual: summary: Multilingual authoring resource value: data: id: clseedsurveycsat000000 workspaceId: clseedworkspace000000000 createdAt: '2026-05-18T09:24:54.014Z' updatedAt: '2026-05-18T09:24:54.014Z' archivedAt: null name: CSAT Survey type: link status: inProgress metadata: {} defaultLanguage: en-US languages: - code: en-US default: true enabled: true - code: de-DE alias: german default: false enabled: false welcomeCard: enabled: false blocks: - id: e0tfwzqk63op37y14z95qq3k name: Main Block elements: - id: nzte4cm8836hgjw63pesziht type: rating range: 5 scale: smiley headline: en-US: How satisfied are you with our product? de-DE: Wie zufrieden sind Sie mit unserem Produkt? required: true endings: [] hiddenFields: enabled: false variables: [] filtered: summary: Language-filtered projection with ?lang=de-DE value: data: id: clseedsurveycsat000000 workspaceId: clseedworkspace000000000 createdAt: '2026-05-18T09:24:54.014Z' updatedAt: '2026-05-18T09:24:54.014Z' archivedAt: null name: CSAT Survey type: link status: inProgress metadata: {} defaultLanguage: en-US languages: - code: en-US default: true enabled: true - code: de-DE alias: german default: false enabled: false welcomeCard: enabled: false blocks: - id: e0tfwzqk63op37y14z95qq3k name: Main Block elements: - id: nzte4cm8836hgjw63pesziht type: rating range: 5 scale: smiley headline: de-DE: Wie zufrieden sind Sie mit unserem Produkt? required: true endings: [] hiddenFields: enabled: false variables: [] '400': description: Invalid survey id, unsupported query parameter, unknown language, or unsupported legacy survey shape content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] patch: operationId: patchSurveyV3 summary: Update a survey description: | Updates a survey through a strict top-level partial document. Omitted top-level fields are preserved. Provided top-level objects and arrays replace that whole subtree; this endpoint does not deep-merge nested objects and does not implement JSON Patch. This write endpoint accepts the canonical multilingual authoring document only. Translatable fields must be locale-code maps, not strings. `PATCH ?lang=...` and `?version=...` are not supported in Scope 1. For MCP and AI-agent workflows, fetch the current survey first, modify only the intended top-level fields, call `/api/v3/surveys/validate` with `operation: patch` for preflight, then submit the same patch here. Missing ids in replaced arrays are treated as deletions. Variable name renames are allowed but may be destructive for downstream exports or integrations. Element id changes are rejected once the survey and element are no longer drafts. Optimistic concurrency (`ETag`, `If-Match`, or revision tokens) is future work; until then, overlapping writes are last-write-wins. tags: - V3 Surveys parameters: - in: path name: surveyId required: true schema: type: string format: cuid2 description: Survey identifier. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PatchSurveyRequest' examples: renameSurvey: summary: Rename a survey value: name: Updated Product Feedback replaceMetadata: summary: Replace metadata value: metadata: title: en-US: Product Feedback de-DE: Produktfeedback cx_operation: product_feedback replaceBlocks: summary: Replace survey blocks value: blocks: - id: clbk1234567890123456789012 name: Main Block elements: - id: satisfaction type: rating headline: en-US: How satisfied are you? de-DE: Wie zufrieden sind Sie? required: true range: 5 scale: smiley responses: '200': description: Survey 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: $ref: '#/components/schemas/SurveyResource' '400': description: | Bad Request — the patch failed schema validation, i.e. any rule checkable from the request body alone, without consulting stored state. Covers: malformed JSON; an unsupported query parameter; unknown, unsupported, or immutable fields (`type` cannot be changed after creation, and `distribution`/`targeting` are `app`-only — sending them when patching a `link` survey is rejected); wrong types or out-of-range values; bad enum values; malformed multilingual maps; and intra-document field-combination rules — notably `displayLimit` is required (must be >= 1) when `displayOption` is `displaySome`. Cross-reference failures that need stored state to detect return **422** instead. Every offending field is itemized in `invalid_params`. Unknown, forbidden, immutable, or wrongly-combined fields carry `code: unsupported_field` (e.g. `type`, or `distribution`/`targeting` on a link survey); omissions carry `code: missing_required_field`. Bare type, range, enum, and malformed-locale-map violations report the field `name` with a human-readable `reason` and no machine `code`. content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '401': $ref: '#/components/responses/V3Unauthorized' '403': description: Forbidden — no write access, missing external URL permission, missing Contacts entitlement for app-survey targeting, or survey does not exist (404 not used; avoids existence leak) content: application/problem+json: schema: $ref: '#/components/schemas/Problem' '422': description: |- Unprocessable Content — the request cannot be processed against stored state. Two cases: 1. The target survey is archived (soft-deleted) and therefore read-only; restore it before editing. The `invalid_params` entry reports `name: archivedAt` with the reason `This survey is archived. Restore it before editing.` 2. The patched document passed schema validation but failed a cross-reference check that requires stored state to detect: an undeclared locale key used in content, a duplicate stable id, an immutable element-id change on a published survey, a dangling logic/jump reference, an invalid media URL, a `distribution.triggers[].actionClassId` referencing an action class that does not exist in the workspace (discover valid ids via `GET /api/v3/action-classes`), or a `targeting.filters` entry that cannot resolve in the workspace — an unknown contact-attribute key or segment, an unsupported person identifier, or an unknown device value. The `invalid_params` array pinpoints each issue (e.g. `code: invalid_reference` with the offending `identifier`). content: application/problem+json: schema: $ref: '#/components/schemas/Problem' examples: archived: summary: Survey is archived (restore before editing) value: type: about:blank title: Unprocessable Content status: 422 detail: Survey is archived code: unprocessable_content requestId: req_clsv1234567890123456789012 invalid_params: - name: archivedAt reason: This survey is archived. Restore it before editing. invalidReference: summary: Cross-reference validation failed value: type: about:blank title: Unprocessable Content status: 422 detail: Survey document failed validation code: unprocessable_content requestId: req_clsv1234567890123456789012 invalid_params: - name: blocks[0].logic[0].target reason: References a block that does not exist in the survey. code: invalid_reference identifier: e0tfwzqk63op37y14z95qq3k '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] delete: operationId: deleteSurveyV3 summary: Delete a survey description: Deletes a survey by id. Session cookie or x-api-key. tags: - V3 Surveys parameters: - in: path name: surveyId required: true schema: type: string format: cuid2 description: Survey identifier. responses: '204': description: Survey deleted successfully headers: X-Request-Id: schema: type: string description: Request correlation ID Cache-Control: schema: type: string example: private, no-store '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/surveys/{surveyId}/archive: post: operationId: archiveSurveyV3 summary: 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 or x-api-key. tags: - V3 Surveys parameters: - in: path name: surveyId required: true schema: type: string format: cuid2 description: Survey identifier. responses: '200': description: Survey archived 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 required: - id - status - archivedAt properties: id: type: string description: Survey identifier. status: type: string enum: - draft - inProgress - paused - completed description: Current survey status. archivedAt: type: - string - 'null' format: date-time description: Soft-delete/archive marker (ISO 8601). A timestamp after archiving; `null` when the survey is active. examples: archived: summary: Survey archived value: data: id: clsv1234567890123456789012 status: paused archivedAt: '2026-05-18T09:24:54.014Z' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/surveys/{surveyId}/restore: post: operationId: restoreSurveyV3 summary: 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 or x-api-key. tags: - V3 Surveys parameters: - in: path name: surveyId required: true schema: type: string format: cuid2 description: Survey identifier. responses: '200': description: Survey restored 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 required: - id - status - archivedAt properties: id: type: string description: Survey identifier. status: type: string enum: - draft - inProgress - paused - completed description: Current survey status. archivedAt: type: - string - 'null' format: date-time description: Soft-delete/archive marker (ISO 8601). `null` after restoring; a timestamp while the survey is archived. examples: restored: summary: Survey restored value: data: id: clsv1234567890123456789012 status: paused archivedAt: null '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows: get: operationId: listWorkflowsV3 summary: List workflows description: | Returns workflow list items for a workspace, sorted by `sortBy` (default `updatedAt`, newest first). Workflows are workspace-scoped and exposed through the same v3 session-or-`x-api-key` authorization model as surveys. List items are intentionally slim: they include the derived `triggerType` and `surveyId` plus a `lastRun` summary, but not the full `definition`. Use `GET /api/v3/workflows/{workflowId}` to read the definition. `archived` workflows are excluded unless explicitly requested with `filter[status][in]=archived`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkspaceIdQuery' - $ref: '#/components/parameters/LimitQuery' - $ref: '#/components/parameters/CursorQuery' - in: query name: filter[name][contains] schema: type: string maxLength: 512 description: Case-insensitive substring match on workflow name (same semantics as the survey list filter). - in: query name: filter[status][in] schema: type: array items: $ref: '#/components/schemas/WorkflowStatus' style: form explode: true description: | Workflow lifecycle-state filter. Repeat the parameter (`filter[status][in]=draft&filter[status][in]=disabled`) or use comma-separated values (`filter[status][in]=draft,disabled`). Omitting the filter returns every status except `archived`. Invalid values → **400**. - in: query name: sortBy schema: type: string enum: - createdAt - updatedAt - name description: Sort order. Defaults to `updatedAt` (newest first). The `cursor` token is bound to the selected sort order. responses: '200': description: Workflows retrieved 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 - meta properties: data: type: array items: $ref: '#/components/schemas/WorkflowListItem' meta: $ref: '#/components/schemas/CursorPaginationMeta' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] post: operationId: createWorkflowV3 summary: Create a workflow description: | Creates a `draft` workflow. The workflow definition is the public source of truth used by the dashboard and external clients, following the document-first pattern established by the v3 Survey API. Runtime persistence, revision creation, and version snapshotting are implementation details behind this contract. Workflows are always created as drafts; use `POST /api/v3/workflows/{workflowId}/enable` to make them respond to trigger events. Graph reference failures (duplicate node ids, edges pointing at missing nodes, `entryNodeId` not referencing the trigger, unknown `surveyId` or `endingCardIds` in the workspace) are rejected with **400** and structured `invalid_params`. tags: - V3 Workflows requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWorkflowRequest' examples: responseCompletedEmail: summary: Create a response-completed email workflow value: workspaceId: clworkspace000000000000000 name: Notify team after CSAT completion description: Sends an internal email when a respondent reaches the negative CSAT ending. status: draft definition: schemaVersion: 1 trigger: id: trigger type: trigger triggerType: response.completed label: CSAT survey completed config: surveyId: clsurvey000000000000000000 endingCardIds: - clending000000000000000000 nodes: - id: send-email type: action actionType: send_email label: Notify support team config: to: support@example.com from: noreply@example.com replyTo: - support@example.com subject: Thanks for your feedback body: We saw your feedback and will follow up soon. attachResponseData: true includeVariables: true includeHiddenFields: false ui: position: x: 260 'y': 120 edges: - id: edge-trigger-email source: trigger target: send-email entryNodeId: trigger responses: '201': description: Workflow created successfully headers: X-Request-Id: schema: type: string description: Request correlation ID Cache-Control: schema: type: string example: private, no-store Location: schema: type: string example: /api/v3/workflows/clworkflow000000000000000 content: application/json: schema: type: object required: - data properties: data: $ref: '#/components/schemas/WorkflowResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/runs: get: operationId: listWorkflowRunsV3 summary: List workflow runs description: | Returns workflow run summaries for a workspace, newest first (`createdAt` descending). Use `workflowId` to narrow the list to one workflow, or `responseId` to find the runs a specific survey response produced. List items are summaries without `triggerPayload`, `data`, or `logs`; fetch `GET /api/v3/workflows/runs/{runId}` for the full debug-oriented payload. This keeps response-level data out of list reads by default. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkspaceIdQuery' - $ref: '#/components/parameters/LimitQuery' - $ref: '#/components/parameters/CursorQuery' - in: query name: workflowId schema: type: string format: cuid2 description: Optional workflow scoping. Returns only runs of this workflow. - in: query name: responseId schema: type: string format: cuid2 description: Optional response scoping. Returns only runs triggered by this survey response. - in: query name: filter[status][in] schema: type: array items: $ref: '#/components/schemas/WorkflowRunStatus' style: form explode: true description: | Run lifecycle-state filter. Repeat the parameter or use comma-separated values (`filter[status][in]=failed,canceled`). Invalid values → **400**. - in: query name: filter[isDryRun][eq] schema: type: boolean description: | Filter for dry runs created by `POST /api/v3/workflows/{workflowId}/test` (`true`) or for real trigger-initiated runs only (`false`). Omit to return both. responses: '200': description: Workflow runs retrieved 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 - meta properties: data: type: array items: $ref: '#/components/schemas/WorkflowRunListItem' meta: $ref: '#/components/schemas/CursorPaginationMeta' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/runs/{runId}: get: operationId: getWorkflowRunV3 summary: Retrieve a workflow run description: | Returns one workflow run by globally unique run id, including the full debug payload: the captured `triggerPayload`, the run `data` (per-step results), the persisted `logs` trace, and retry metadata (`attempt`, `nextAttemptAt`, `lastErrorAt`). Scope 1 deliberately exposes these as JSON dumps so users and agents can understand exactly what happened in a run. Poll this endpoint after `POST /api/v3/workflows/{workflowId}/test`: runs execute asynchronously and transition `queued` → `running` → `completed`/`failed`/`canceled`. Unknown or inaccessible run ids return `403` rather than `404` to avoid leaking cross-workspace resource existence. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowRunIdPath' responses: '200': description: Workflow run retrieved 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: $ref: '#/components/schemas/WorkflowRunResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}: get: operationId: getWorkflowV3 summary: Retrieve a workflow description: | Returns one workflow definition by id. Unknown or inaccessible workflow ids return `403` rather than `404` to avoid leaking cross-workspace resource existence. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': description: Workflow retrieved 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: $ref: '#/components/schemas/WorkflowResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] patch: operationId: patchWorkflowV3 summary: Update a workflow description: | Updates human-facing workflow metadata and/or the workflow definition. Status changes are handled by lifecycle endpoints (`enable`, `disable`, `archive`, `unarchive`) so the state machine remains explicit. Provided fields replace the corresponding stored fields; this endpoint does not implement JSON Patch. `name` and `description` can be updated in any state except `archived`. The `definition` can only be updated while the workflow is `draft` or `disabled`: the live version of an `enabled` workflow is an immutable snapshot, so definition changes require disabling (or moving to draft) first and re-enabling afterwards. Violations return **422** with code `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. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PatchWorkflowRequest' responses: '200': description: Workflow 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: $ref: '#/components/schemas/WorkflowResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] delete: operationId: deleteWorkflowV3 summary: Delete a workflow description: | Permanently deletes a workflow by id. Deletion cascades to the workflow's versions, runs, and run logs so no response-derived data is left behind (GDPR requirement). This is irreversible; use `POST /api/v3/workflows/{workflowId}/archive` for a reversible soft-delete that keeps history. Returns `204 No Content`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '204': description: Workflow deleted successfully headers: X-Request-Id: schema: type: string description: Request correlation ID Cache-Control: schema: type: string example: private, no-store '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/duplicate: post: operationId: duplicateWorkflowV3 summary: Duplicate a workflow description: | Creates a new `draft` workflow by copying an existing workflow's definition, name (or the provided override), and description. The duplicate receives its own id and timestamps and starts with empty run and version history; the source workflow is unchanged. Archived workflows can be duplicated (the copy starts as a normal draft). tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/DuplicateWorkflowRequest' responses: '201': description: Workflow duplicated successfully headers: X-Request-Id: schema: type: string description: Request correlation ID Cache-Control: schema: type: string example: private, no-store Location: schema: type: string example: /api/v3/workflows/clworkflowcopy000000000 content: application/json: schema: type: object required: - data properties: data: $ref: '#/components/schemas/WorkflowResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/enable: post: operationId: enableWorkflowV3 summary: Enable a workflow description: | Moves a workflow from `draft` or `disabled` to `enabled`. Only enabled workflows respond to trigger events. Enabling validates that the definition is executable — the trigger has exactly one outgoing edge (trigger-only drafts cannot be enabled), the graph is acyclic, every node is reachable from the trigger, only Scope 1 node types are present, the referenced survey and ending cards still exist, and every `send_email` action addressing a *literal* email address targets someone who can access this workspace (an element-id `to` resolves to the respondent's own address and is exempt) — and then snapshots an immutable workflow version. Runs reference that snapshot via `workflowVersionId`, so later draft edits never change what an enabled workflow executes. Validation failures return **422** with code `workflow_not_executable` and structured `invalid_params`; calling enable in a state other than `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. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': $ref: '#/components/responses/WorkflowResourceResponse' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '409': $ref: '#/components/responses/V3Conflict' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/disable: post: operationId: disableWorkflowV3 summary: Disable a workflow description: | Moves an `enabled` workflow to `disabled`. Disabled workflows keep their configuration but do not respond to trigger events; runs that are already queued or running are not canceled. Calling disable in any other state returns **422** with code `invalid_workflow_state`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': $ref: '#/components/responses/WorkflowResourceResponse' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/archive: post: operationId: archiveWorkflowV3 summary: Archive a workflow description: | Soft-deletes a workflow by moving it from any non-archived state to `archived`. Archived workflows stop responding to trigger events immediately, become read-only, and are excluded from list responses unless requested with `filter[status][in]=archived`. Their runs, version history, and definition are preserved, and the workflow can be restored with `POST /api/v3/workflows/{workflowId}/unarchive`. Archiving an already archived workflow returns **422** with code `invalid_workflow_state`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': $ref: '#/components/responses/WorkflowResourceResponse' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/unarchive: post: operationId: unarchiveWorkflowV3 summary: Unarchive a workflow description: | Restores an `archived` workflow to `draft`. Unarchiving never re-enables a workflow automatically: re-enable explicitly with `POST /api/v3/workflows/{workflowId}/enable`, which re-validates the definition (the referenced survey or ending cards may have changed while the workflow was archived). Calling unarchive on a non-archived workflow returns **422** with code `invalid_workflow_state`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': $ref: '#/components/responses/WorkflowResourceResponse' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/workflows/{workflowId}/test: post: operationId: testWorkflowV3 summary: 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. The 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. Drafts 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`. tags: - V3 Workflows parameters: - $ref: '#/components/parameters/WorkflowIdPath' responses: '200': description: | Validation result. The request succeeded; inspect `data.ok` to see whether the workflow is ready, and `data.problems` for any issues. 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: $ref: '#/components/schemas/WorkflowTestResult' examples: ok: summary: Workflow is valid and ready to enable value: data: workflowId: clworkflow0000000000000000 ok: true problems: [] problems: summary: Workflow has unresolved trigger references value: data: workflowId: clworkflow0000000000000000 ok: false problems: - code: survey_not_found field: definition.trigger.config.surveyId message: The referenced survey does not exist in this workspace. - code: ending_card_not_found field: definition.trigger.config.endingCardIds message: Ending card clending000000000000000000 does not exist on the survey. recipientNotAllowed: summary: A send_email action addresses a literal recipient without workspace access value: data: workflowId: clworkflow0000000000000000 ok: false problems: - code: recipient_not_allowed field: definition.nodes.config.to message: Recipient outsider@external.example cannot access this workspace. A send_email action may only address someone with access to this workspace or a respondent field. '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/action-classes: get: operationId: listActionClassesV3 summary: List action classes description: | Returns the action classes (user actions that can trigger an app survey) for a workspace. Use the returned ids as `distribution.triggers[].actionClassId` when creating or updating app surveys. Read-only: this v3 endpoint lists action classes but does not create them — action classes are managed in the Formbricks app (Actions settings) or via the v1/v2 management API. An empty `data` array means the workspace has no action classes yet. Triggers are optional, so you can still create an app survey with `distribution.triggers: []` (or omit `distribution` entirely) and attach triggers later via `PATCH` once an action class exists. Session cookie or x-api-key. tags: - V3 Action Classes parameters: - in: query name: workspaceId required: true schema: type: string format: cuid2 description: | Workspace identifier. This is the canonical container ID for v3 APIs. - in: query name: limit schema: type: integer minimum: 1 maximum: 100 default: 50 description: Page size (max 100). - in: query name: cursor schema: type: string description: | Opaque cursor returned as `meta.nextCursor` from the previous page. Omit on the first request. responses: '200': description: Action classes retrieved 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 - meta properties: data: type: array items: $ref: '#/components/schemas/ActionClassResource' meta: type: object required: - limit - nextCursor properties: limit: type: integer nextCursor: type: - string - 'null' description: Opaque cursor for the next page. `null` when there are no more results. '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/contact-attribute-keys: get: operationId: listContactAttributeKeysV3 summary: List contact attribute keys description: | Returns the contact attribute keys for a workspace. Use the returned `key` values as `targeting.filters[].root.contactAttributeKey` when targeting app surveys; `dataType` hints which operators apply. Session cookie or x-api-key. (Applying targeting filters requires the Contacts entitlement; this discovery endpoint does not.) tags: - V3 Contact Attribute Keys parameters: - in: query name: workspaceId required: true schema: type: string format: cuid2 description: | Workspace identifier. This is the canonical container ID for v3 APIs. - in: query name: limit schema: type: integer minimum: 1 maximum: 100 default: 50 description: Page size (max 100). - in: query name: cursor schema: type: string description: | Opaque cursor returned as `meta.nextCursor` from the previous page. Omit on the first request. responses: '200': description: Contact attribute keys retrieved 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 - meta properties: data: type: array items: $ref: '#/components/schemas/ContactAttributeKeyResource' meta: type: object required: - limit - nextCursor properties: limit: type: integer nextCursor: type: - string - 'null' description: Opaque cursor for the next page. `null` when there are no more results. '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] - apiKeyAuth: [] /api/v3/tags: get: operationId: listTagsV3 summary: List tags description: | Returns a workspace's response tags, each with the number of responses currently carrying it. Session cookie only. Tags are managed from the Formbricks app (Workspace settings → Tags); these endpoints back that screen, so they do not accept `x-api-key`. tags: - V3 Tags parameters: - in: query name: workspaceId required: true schema: type: string format: cuid2 description: | Workspace identifier. This is the canonical container ID for v3 APIs. responses: '200': description: Tags retrieved 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: array items: $ref: '#/components/schemas/TagResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] /api/v3/tags/{tagId}: patch: operationId: renameTagV3 summary: Rename a tag description: | Renames a tag. The workspace is resolved from the tag itself, so the caller cannot choose which scope it is authorized against. Requires organization owner/manager, or a workspace team membership with read-write permission. An unknown `tagId` answers `403`, not `404`, so tag ids cannot be probed for existence. A name that collides with another tag in the same workspace answers `422`. tags: - V3 Tags parameters: - in: path name: tagId required: true schema: type: string format: cuid2 requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - name properties: name: type: string minLength: 1 maxLength: 255 description: New label. Trimmed; must not be empty. responses: '200': description: Tag renamed successfully content: application/json: schema: type: object required: - data properties: data: $ref: '#/components/schemas/TagResource' '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '422': $ref: '#/components/responses/V3UnprocessableContent' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] delete: operationId: deleteTagV3 summary: Delete a tag description: | Deletes a tag and removes it from every response carrying it. Same authorization as rename. tags: - V3 Tags parameters: - in: path name: tagId required: true schema: type: string format: cuid2 responses: '200': description: Tag deleted successfully content: application/json: schema: type: object required: - data properties: data: type: object required: - id properties: id: type: string format: cuid2 description: The deleted tag's id. '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] /api/v3/tags/{tagId}/merge: post: operationId: mergeTagsV3 summary: Merge a tag into another description: | Moves every response tagged `{tagId}` onto `newTagId`, then deletes `{tagId}`. Both tags must belong to the same workspace — a cross-workspace merge answers `403`, since it would move responses across a tenant boundary. Same authorization as rename. Merging a tag into itself answers `400`. tags: - V3 Tags parameters: - in: path name: tagId required: true schema: type: string format: cuid2 description: The tag being merged away. requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - newTagId properties: newTagId: type: string format: cuid2 description: The surviving tag. responses: '200': description: Tags merged successfully content: application/json: schema: type: object required: - data properties: data: type: object required: - id properties: id: type: string format: cuid2 description: The surviving tag's id. '400': $ref: '#/components/responses/V3BadRequest' '401': $ref: '#/components/responses/V3Unauthorized' '403': $ref: '#/components/responses/V3Forbidden' '429': $ref: '#/components/responses/V3TooManyRequests' '500': $ref: '#/components/responses/V3InternalServerError' security: - sessionAuth: [] components: securitySchemes: sessionAuth: type: apiKey in: cookie name: next-auth.session-token description: | 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. 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. schemas: SurveyListItem: type: object description: | Shape returned by `GET /api/v3/surveys`. Serialized dates are ISO 8601 strings. The v3 overview contract intentionally omits internal fields such as `_count`. Legacy DB rows may include survey **type** values `website` or `web` (see Prisma); filter **type** only accepts `link` | `app`. required: - id - name - workspaceId - type - status - publishOn - createdAt - updatedAt - archivedAt - responseCount - completedResponseCount - creator properties: id: type: string name: type: string workspaceId: type: string type: type: string enum: - link - app - website - web status: type: string enum: - draft - inProgress - paused - completed createdAt: type: string format: date-time updatedAt: type: string format: date-time archivedAt: type: - string - 'null' format: date-time description: Soft-delete/archive marker (ISO 8601); `null` when the survey is active. publishOn: type: - string - 'null' format: date-time description: Scheduled publish time (ISO 8601), or null if not scheduled. responseCount: type: integer description: Number of responses, including partial ones. completedResponseCount: type: integer description: Number of responses the respondent finished. creator: type: - object - 'null' description: The user who created the survey, or null for API-key/system-created surveys. required: - name properties: name: type: string additionalProperties: false InvalidParam: type: object description: | Field-level validation error. `name` and `reason` are always intended for humans. Optional machine-readable fields are included when the API can identify the semantic error, especially for survey locale, identifier, and reference validation used by MCP and agent clients. required: - name - reason properties: name: type: string description: Dot-path to the invalid request field. reason: type: string description: Human-readable repair guidance. code: type: string enum: - dangling_reference - duplicate_identifier - duplicate_locale - forbidden_identifier - immutable_identifier - invalid_graph - invalid_locale - invalid_reference - missing_required_field - missing_translation - unsupported_field - unsupported_locale description: | Machine-readable validation category, when available. `invalid_graph` covers workflow graph-structure violations such as a duplicated trigger edge, an unreachable node, or a cycle. identifier: type: string description: Identifier involved in the validation failure, when available. For language errors, this can be the normalized language code. referenceType: type: string enum: - block - element - ending - hiddenField - language - variable - variableName - recall - node - edge - survey - endingCard description: Identifier namespace involved in the validation failure. `node`, `edge`, `survey`, and `endingCard` are used by workflow validation. missingId: type: string description: Referenced id that does not exist in the submitted survey document. firstUsedAt: type: string description: Dot-path where a duplicate identifier was first used. conflictsWith: type: string description: Dot-path to the existing identifier that conflicts with this field. additionalProperties: false Problem: 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`. required: - title - status - detail - requestId properties: type: type: string format: uri title: type: string status: type: integer detail: type: string instance: type: string code: type: string enum: - ai_features_not_enabled - ai_generated_payload_invalid - ai_instance_not_configured - ai_output_too_long - ai_smart_tools_disabled - bad_gateway - bad_request - conflict - forbidden - internal_server_error - invalid_workflow_state - not_authenticated - not_found - payload_too_large - service_unavailable - too_many_requests - unprocessable_content - workflow_not_executable requestId: type: string details: type: object invalid_params: type: array items: $ref: '#/components/schemas/InvalidParam' TranslatableTextMap: type: object description: Multilingual text map keyed by the emitted `languages[].code` values for this survey. propertyNames: type: string description: Survey language code/tag, for example `en-US`, `de-DE`, or `zh-Hans-CN`. additionalProperties: type: string TranslatableText: allOf: - $ref: '#/components/schemas/TranslatableTextMap' description: | Survey authoring text. `GET /api/v3/surveys/{surveyId}` always returns maps keyed by the emitted `languages[].code` values for this survey. Use `?lang=` to filter which language keys are included. The internal storage key `default` is never exposed by v3. examples: - en-US: What should we improve? de-DE: Was sollten wir verbessern? SurveyMetadata: type: object description: | Arbitrary JSON survey context for customer- or operation-specific metadata. v3 preserves arbitrary metadata values as-is. If present, `title` and `description` are treated as translatable text maps and returned with real locale-code keys. properties: title: $ref: '#/components/schemas/TranslatableText' description: $ref: '#/components/schemas/TranslatableText' additionalProperties: true LocaleCode: type: string pattern: ^[a-z]{2}(?:-[A-Z][a-z]{3})?-[A-Z]{2}$ description: Canonical locale code accepted by v3 survey APIs, for example `en-US`, `de-DE`, or `zh-Hans-CN`. example: en-US CreateSurveyLanguage: type: object description: Language to connect to the created survey. `default`, if provided, must match `defaultLanguage`. required: - code properties: code: $ref: '#/components/schemas/LocaleCode' default: type: boolean description: Optional marker for readability; only the `defaultLanguage` entry may set this to true. enabled: type: boolean default: true description: Whether this language is enabled for respondent-facing delivery. additionalProperties: false SurveyWelcomeCard: type: object description: Optional card shown before the first survey block. required: - enabled properties: enabled: type: boolean headline: $ref: '#/components/schemas/TranslatableText' subheader: $ref: '#/components/schemas/TranslatableText' buttonLabel: $ref: '#/components/schemas/TranslatableText' fileUrl: type: string videoUrl: type: string timeToFinish: type: boolean showResponseCount: type: boolean additionalProperties: false SurveyElementBase: type: object required: - id - type - headline - required properties: id: type: string pattern: ^[a-zA-Z0-9_-]+$ description: Stable element id. Avoid spaces and reserved ids. type: type: string enum: - openText - multipleChoiceSingle - multipleChoiceMulti - nps - rating - csat - ces - consent - pictureSelection - cta - date - fileUpload - cal - matrix - address - ranking - contactInfo headline: $ref: '#/components/schemas/TranslatableText' subheader: $ref: '#/components/schemas/TranslatableText' required: type: boolean imageUrl: type: string videoUrl: type: string isDraft: type: boolean description: Draft marker used by the editor and future update rules. SurveyCharLimit: type: object description: Optional `openText` character limit configuration. properties: enabled: type: boolean default: false min: type: number max: type: number additionalProperties: false SurveyValidationRule: type: object required: - id - type - params properties: id: type: string type: type: string enum: - minLength - maxLength - pattern - email - url - phone - equals - doesNotEqual - contains - doesNotContain - minValue - maxValue - isGreaterThan - isLessThan - minSelections - maxSelections - minRanked - rankAll - minRowsAnswered - answerAllRows - isLaterThan - isEarlierThan - isBetween - isNotBetween - fileExtensionIs - fileExtensionIsNot params: type: object additionalProperties: true field: type: string enum: - addressLine1 - addressLine2 - city - state - zip - country - firstName - lastName - email - phone - company additionalProperties: false SurveyValidation: type: object description: Optional element-level validation rules. required: - rules properties: logic: type: string enum: - and - or default: and rules: type: array items: $ref: '#/components/schemas/SurveyValidationRule' additionalProperties: false SurveyOpenTextElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object properties: type: type: string enum: - openText placeholder: $ref: '#/components/schemas/TranslatableText' longAnswer: type: boolean inputType: type: string enum: - text - email - url - number - phone default: text insightsEnabled: type: boolean default: false charLimit: $ref: '#/components/schemas/SurveyCharLimit' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyConsentElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - label properties: type: type: string enum: - consent label: $ref: '#/components/schemas/TranslatableText' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyChoice: type: object required: - id - label properties: id: type: string description: Stable choice id. label: $ref: '#/components/schemas/TranslatableText' additionalProperties: false SurveyShuffleOption: type: string enum: - none - all - exceptLast - reverseOrderOccasionally - reverseOrderExceptLast SurveyMultipleChoiceDisplayType: type: string enum: - list - dropdown SurveyMultipleChoiceSingleElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - choices properties: type: type: string enum: - multipleChoiceSingle choices: type: array minItems: 2 items: $ref: '#/components/schemas/SurveyChoice' shuffleOption: $ref: '#/components/schemas/SurveyShuffleOption' otherOptionPlaceholder: $ref: '#/components/schemas/TranslatableText' displayType: $ref: '#/components/schemas/SurveyMultipleChoiceDisplayType' unevaluatedProperties: false SurveyMultipleChoiceMultiElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - choices properties: type: type: string enum: - multipleChoiceMulti choices: type: array minItems: 2 items: $ref: '#/components/schemas/SurveyChoice' shuffleOption: $ref: '#/components/schemas/SurveyShuffleOption' otherOptionPlaceholder: $ref: '#/components/schemas/TranslatableText' validation: $ref: '#/components/schemas/SurveyValidation' displayType: $ref: '#/components/schemas/SurveyMultipleChoiceDisplayType' unevaluatedProperties: false SurveyNpsElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object properties: type: type: string enum: - nps lowerLabel: $ref: '#/components/schemas/TranslatableText' upperLabel: $ref: '#/components/schemas/TranslatableText' isColorCodingEnabled: type: boolean default: false unevaluatedProperties: false SurveyCtaElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object description: If `buttonExternal` is true, `buttonUrl` and `ctaButtonLabel` are required. properties: type: type: string enum: - cta buttonExternal: type: boolean default: false buttonUrl: type: string ctaButtonLabel: $ref: '#/components/schemas/TranslatableText' - if: required: - buttonExternal properties: buttonExternal: const: true then: required: - buttonUrl - ctaButtonLabel unevaluatedProperties: false SurveyRatingElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - scale - range properties: type: type: string enum: - rating scale: type: string enum: - number - smiley - star range: type: integer enum: - 3 - 4 - 5 - 6 - 7 - 10 lowerLabel: $ref: '#/components/schemas/TranslatableText' upperLabel: $ref: '#/components/schemas/TranslatableText' isColorCodingEnabled: type: boolean default: false unevaluatedProperties: false SurveyPictureChoice: type: object required: - id - imageUrl properties: id: type: string description: Stable picture choice id. imageUrl: type: string additionalProperties: false SurveyPictureSelectionElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - choices properties: type: type: string enum: - pictureSelection allowMulti: type: boolean default: false choices: type: array minItems: 2 items: $ref: '#/components/schemas/SurveyPictureChoice' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyDateElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - format properties: type: type: string enum: - date html: $ref: '#/components/schemas/TranslatableText' format: type: string enum: - M-d-y - d-M-y - y-M-d validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyFileUploadElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - allowMultipleFiles properties: type: type: string enum: - fileUpload allowMultipleFiles: type: boolean maxSizeInMB: type: number allowedFileExtensions: type: array items: type: string validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyCalElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - calUserName properties: type: type: string enum: - cal calUserName: type: string minLength: 1 calHost: type: string unevaluatedProperties: false SurveyMatrixElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - rows - columns properties: type: type: string enum: - matrix rows: type: array items: $ref: '#/components/schemas/SurveyChoice' columns: type: array items: $ref: '#/components/schemas/SurveyChoice' shuffleOption: $ref: '#/components/schemas/SurveyShuffleOption' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyToggleInputConfig: type: object description: Field config for address and contact info elements. required: - show - required - placeholder properties: show: type: boolean required: type: boolean placeholder: $ref: '#/components/schemas/TranslatableText' additionalProperties: false SurveyAddressElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - addressLine1 - addressLine2 - city - state - zip - country properties: type: type: string enum: - address addressLine1: $ref: '#/components/schemas/SurveyToggleInputConfig' addressLine2: $ref: '#/components/schemas/SurveyToggleInputConfig' city: $ref: '#/components/schemas/SurveyToggleInputConfig' state: $ref: '#/components/schemas/SurveyToggleInputConfig' zip: $ref: '#/components/schemas/SurveyToggleInputConfig' country: $ref: '#/components/schemas/SurveyToggleInputConfig' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyRankingElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - choices properties: type: type: string enum: - ranking choices: type: array minItems: 2 maxItems: 25 items: $ref: '#/components/schemas/SurveyChoice' otherOptionPlaceholder: $ref: '#/components/schemas/TranslatableText' shuffleOption: $ref: '#/components/schemas/SurveyShuffleOption' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyContactInfoElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - firstName - lastName - email - phone - company properties: type: type: string enum: - contactInfo firstName: $ref: '#/components/schemas/SurveyToggleInputConfig' lastName: $ref: '#/components/schemas/SurveyToggleInputConfig' email: $ref: '#/components/schemas/SurveyToggleInputConfig' phone: $ref: '#/components/schemas/SurveyToggleInputConfig' company: $ref: '#/components/schemas/SurveyToggleInputConfig' validation: $ref: '#/components/schemas/SurveyValidation' unevaluatedProperties: false SurveyCsatElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - scale - range properties: type: type: string enum: - csat scale: type: string enum: - number - smiley - star range: type: integer enum: - 5 lowerLabel: $ref: '#/components/schemas/TranslatableText' upperLabel: $ref: '#/components/schemas/TranslatableText' isColorCodingEnabled: type: boolean default: false unevaluatedProperties: false SurveyCesElement: allOf: - $ref: '#/components/schemas/SurveyElementBase' - type: object required: - scale - range properties: type: type: string enum: - ces scale: type: string enum: - number - smiley - star range: type: integer enum: - 5 - 7 lowerLabel: $ref: '#/components/schemas/TranslatableText' upperLabel: $ref: '#/components/schemas/TranslatableText' isColorCodingEnabled: type: boolean default: false unevaluatedProperties: false SurveyElement: oneOf: - $ref: '#/components/schemas/SurveyOpenTextElement' - $ref: '#/components/schemas/SurveyConsentElement' - $ref: '#/components/schemas/SurveyMultipleChoiceSingleElement' - $ref: '#/components/schemas/SurveyMultipleChoiceMultiElement' - $ref: '#/components/schemas/SurveyNpsElement' - $ref: '#/components/schemas/SurveyCtaElement' - $ref: '#/components/schemas/SurveyRatingElement' - $ref: '#/components/schemas/SurveyPictureSelectionElement' - $ref: '#/components/schemas/SurveyDateElement' - $ref: '#/components/schemas/SurveyFileUploadElement' - $ref: '#/components/schemas/SurveyCalElement' - $ref: '#/components/schemas/SurveyMatrixElement' - $ref: '#/components/schemas/SurveyAddressElement' - $ref: '#/components/schemas/SurveyRankingElement' - $ref: '#/components/schemas/SurveyContactInfoElement' - $ref: '#/components/schemas/SurveyCsatElement' - $ref: '#/components/schemas/SurveyCesElement' discriminator: propertyName: type mapping: openText: '#/components/schemas/SurveyOpenTextElement' consent: '#/components/schemas/SurveyConsentElement' multipleChoiceSingle: '#/components/schemas/SurveyMultipleChoiceSingleElement' multipleChoiceMulti: '#/components/schemas/SurveyMultipleChoiceMultiElement' nps: '#/components/schemas/SurveyNpsElement' cta: '#/components/schemas/SurveyCtaElement' rating: '#/components/schemas/SurveyRatingElement' pictureSelection: '#/components/schemas/SurveyPictureSelectionElement' date: '#/components/schemas/SurveyDateElement' fileUpload: '#/components/schemas/SurveyFileUploadElement' cal: '#/components/schemas/SurveyCalElement' matrix: '#/components/schemas/SurveyMatrixElement' address: '#/components/schemas/SurveyAddressElement' ranking: '#/components/schemas/SurveyRankingElement' contactInfo: '#/components/schemas/SurveyContactInfoElement' csat: '#/components/schemas/SurveyCsatElement' ces: '#/components/schemas/SurveyCesElement' description: | Survey element/question inside a block. Element ids are stable public identifiers used by logic, recall strings, response data, quotas, integrations, and analysis. `type` selects the allowed shape; unsupported fields are rejected instead of ignored. SurveyDynamicReference: type: object description: Dynamic reference to another value in the survey document. required: - type - value properties: type: type: string enum: - element - variable - hiddenField value: type: string description: Element id, variable id, or hidden field id depending on `type`. meta: type: object additionalProperties: type: string additionalProperties: false SurveyLogicOperand: oneOf: - type: object required: - type - value properties: type: type: string enum: - static value: oneOf: - type: string - type: number - type: array items: type: string additionalProperties: false - $ref: '#/components/schemas/SurveyDynamicReference' SurveyCondition: type: object description: | Single condition. Operators such as `isSubmitted`, `isSkipped`, `isClicked`, `isAccepted`, `isBooked`, `isSet`, and `isEmpty` do not use `rightOperand`; comparison operators do. required: - id - leftOperand - operator properties: id: type: string format: cuid2 leftOperand: $ref: '#/components/schemas/SurveyDynamicReference' operator: type: string enum: - equals - doesNotEqual - contains - doesNotContain - startsWith - doesNotStartWith - endsWith - doesNotEndWith - isSubmitted - isSkipped - isGreaterThan - isLessThan - isGreaterThanOrEqual - isLessThanOrEqual - equalsOneOf - includesAllOf - includesOneOf - doesNotIncludeOneOf - doesNotIncludeAllOf - isClicked - isNotClicked - isAccepted - isBefore - isAfter - isBooked - isPartiallySubmitted - isCompletelySubmitted - isSet - isNotSet - isEmpty - isNotEmpty - isAnyOf rightOperand: $ref: '#/components/schemas/SurveyLogicOperand' additionalProperties: false SurveyConditionGroup: type: object required: - id - connector - conditions properties: id: type: string format: cuid2 connector: type: string enum: - and - or conditions: type: array items: oneOf: - $ref: '#/components/schemas/SurveyCondition' - $ref: '#/components/schemas/SurveyConditionGroup' additionalProperties: false SurveyCalculateAction: type: object description: Updates a survey variable when the logic rule matches. required: - id - objective - variableId - operator - value properties: id: type: string format: cuid2 objective: type: string enum: - calculate variableId: type: string format: cuid2 description: Variable id for `calculate`. operator: type: string enum: - assign - concat - add - subtract - multiply - divide value: $ref: '#/components/schemas/SurveyLogicOperand' additionalProperties: false SurveyRequireAnswerAction: type: object description: Requires an element/question to be answered before continuing. required: - id - objective - target properties: id: type: string format: cuid2 objective: type: string enum: - requireAnswer target: type: string description: Target element id. additionalProperties: false SurveyJumpToBlockAction: type: object description: Jumps to another block or ending when the logic rule matches. required: - id - objective - target properties: id: type: string format: cuid2 objective: type: string enum: - jumpToBlock target: type: string format: cuid2 description: Target block id or ending id. additionalProperties: false SurveyLogicAction: oneOf: - $ref: '#/components/schemas/SurveyCalculateAction' - $ref: '#/components/schemas/SurveyRequireAnswerAction' - $ref: '#/components/schemas/SurveyJumpToBlockAction' description: | Logic action. Keep referenced ids stable: `calculate.variableId` points to a variable id, `requireAnswer.target` points to an element id, and `jumpToBlock.target` points to a block id or ending id. SurveyBlockLogic: type: object description: Conditional logic rule evaluated at block level. required: - id - conditions - actions properties: id: type: string format: cuid2 conditions: $ref: '#/components/schemas/SurveyConditionGroup' actions: type: array items: $ref: '#/components/schemas/SurveyLogicAction' additionalProperties: false CreateSurveyBlock: type: object description: | Block accepted by `POST /api/v3/surveys`. `id` may be omitted and will be generated by the server. Provide an explicit cuid2 id when logic in the same request needs to jump to this block. For normal sequential flow, omit `logic` and `logicFallback`. `logicFallback` is only valid when this same block has at least one `logic` rule. required: - name - elements properties: id: type: string format: cuid2 description: Optional stable block id. Generated when omitted. name: type: string minLength: 1 elements: type: array minItems: 1 items: $ref: '#/components/schemas/SurveyElement' logic: type: array items: $ref: '#/components/schemas/SurveyBlockLogic' logicFallback: type: string format: cuid2 description: | Block or ending id used when no logic condition matches. Only valid when this same block has at least one `logic` rule; omit it for normal sequential flow. buttonLabel: $ref: '#/components/schemas/TranslatableText' backButtonLabel: $ref: '#/components/schemas/TranslatableText' additionalProperties: false SurveyEndScreenEnding: type: object description: Visual end screen displayed after survey completion. required: - id - type properties: id: type: string format: cuid2 description: Stable ending id. `jumpToBlock.target` may point to this id. type: type: string enum: - endScreen headline: $ref: '#/components/schemas/TranslatableText' subheader: $ref: '#/components/schemas/TranslatableText' buttonLabel: $ref: '#/components/schemas/TranslatableText' buttonLink: type: string imageUrl: type: string videoUrl: type: string additionalProperties: false SurveyRedirectEnding: type: object description: | Redirects the respondent to a URL after survey completion. External redirects require the organization's external URL permission; otherwise write endpoints return `403 Forbidden`. required: - id - type - url properties: id: type: string format: cuid2 description: Stable ending id. `jumpToBlock.target` may point to this id. type: type: string enum: - redirectToUrl url: type: string format: uri description: External redirect URL. Requires the organization's external URL permission. label: type: string description: Optional internal label for redirect endings. additionalProperties: false SurveyEnding: oneOf: - $ref: '#/components/schemas/SurveyEndScreenEnding' - $ref: '#/components/schemas/SurveyRedirectEnding' discriminator: propertyName: type mapping: endScreen: '#/components/schemas/SurveyEndScreenEnding' redirectToUrl: '#/components/schemas/SurveyRedirectEnding' description: | Ending reached after the last block or a jump action. `type` selects the allowed shape; unsupported fields are rejected instead of ignored. SurveyHiddenFields: type: object description: | Hidden fields, sometimes called embedded data in other survey products. Field ids are stable public identifiers and may be referenced by logic, recall, quotas, integrations, and response data. Use only letters, numbers, underscores, and hyphens; avoid spaces and reserved ids. required: - enabled properties: enabled: type: boolean fieldIds: type: array items: type: string pattern: ^[a-zA-Z0-9_-]+$ uniqueItems: true additionalProperties: false CreateSurveyNumberVariable: type: object required: - name - type - value properties: id: type: string format: cuid2 description: Optional stable variable id. Generated when omitted. name: type: string pattern: ^[a-z0-9_]+$ description: Unique variable name. Lowercase letters, numbers, and underscores only. type: type: string enum: - number value: type: number description: Default numeric value. additionalProperties: false CreateSurveyTextVariable: type: object required: - name - type - value properties: id: type: string format: cuid2 description: Optional stable variable id. Generated when omitted. name: type: string pattern: ^[a-z0-9_]+$ description: Unique variable name. Lowercase letters, numbers, and underscores only. type: type: string enum: - text value: type: string description: Default text value. additionalProperties: false CreateSurveyVariable: oneOf: - $ref: '#/components/schemas/CreateSurveyNumberVariable' - $ref: '#/components/schemas/CreateSurveyTextVariable' description: | Survey variable accepted by `POST /api/v3/surveys`. `id` may be omitted and will be generated by the server. Provide an explicit cuid2 id when logic in the same request needs to reference this variable. SurveyTrigger: type: object description: | Reference to an existing workspace action class that triggers this app survey. Discover available action-class ids with `GET /api/v3/action-classes`. required: - actionClassId properties: actionClassId: type: string format: cuid2 description: Id of an action class in the same workspace. additionalProperties: false SurveyDistribution: type: object description: | App-survey runtime/display settings. Only valid for `type: app`; rejected for link surveys. **Replacement semantics (destructive):** when `distribution` is provided on PATCH it fully replaces the stored object — any sub-field you omit is reset to its default, NOT preserved. In particular, omitting `triggers` removes ALL existing triggers, and omitting `displayOption` resets it to `displayOnce`. To change one setting, send the complete desired distribution. To leave distribution untouched, omit the whole `distribution` key. properties: displayOption: type: string enum: - displayOnce - displayMultiple - respondMultiple - displaySome default: displayOnce description: | How often the survey may be shown to a contact. `displaySome` shows it up to `displayLimit` times (or until the contact responds, whichever comes first). displayPercentage: type: - number - 'null' minimum: 0.01 maximum: 100 default: null description: | Independent throttle: show the survey to only this percentage of triggered contacts. Optional and valid with any `displayOption`; null/omitted means no throttle (shown to everyone). displayLimit: type: - integer - 'null' minimum: 0 default: null description: | Maximum number of times the survey is shown to a single contact. Required (must be >= 1) when `displayOption` is `displaySome`; otherwise optional. recontactDays: type: - integer - 'null' minimum: 0 default: null description: Days to wait before showing this survey again to the same contact. Null uses the workspace default. autoClose: type: - integer - 'null' minimum: 0 default: null description: Seconds of inactivity after which the survey widget auto-closes. Null disables auto-close. autoComplete: type: - integer - 'null' minimum: 1 default: null description: Automatically stop collecting responses after this many completed responses. Null disables it. delay: type: integer minimum: 0 default: 0 description: Seconds to wait after a trigger fires before showing the survey. triggers: type: array default: [] items: $ref: '#/components/schemas/SurveyTrigger' description: | Action classes that trigger the survey. Every id must reference an existing workspace action class. On PATCH this list fully replaces the survey's triggers — omitting it (or sending `[]`) removes all existing triggers. additionalProperties: false example: displayOption: displaySome displayLimit: 3 displayPercentage: null recontactDays: 7 autoClose: null autoComplete: null delay: 0 triggers: - actionClassId: clyy1234567890123456789012 SegmentFilterValue: description: | Comparison value for a segment filter condition. A string or number for most operators; a relative-date object (`{ amount, unit }`) for `isOlderThan` / `isNewerThan`; a two-element `[from, to]` string array for `isBetween`. Ignored for `isSet` / `isNotSet` but still required. oneOf: - type: string - type: number - type: object required: - amount - unit properties: amount: type: number unit: type: string enum: - days - weeks - months - years additionalProperties: false - type: array items: type: string minItems: 2 maxItems: 2 SegmentFilter: description: | A single targeting condition. `root.type` selects the subject and determines the valid `qualifier.operator` set: `attribute` (a workspace contact-attribute key), `person` (a built-in person identifier such as `userId`/`email`), `segment` (membership in another segment), or `device`. Each variant is one member of this union; the matching member is chosen by `root.type`. oneOf: - title: AttributeFilter description: Matches on a workspace contact-attribute value. type: object required: - id - root - qualifier - value properties: id: type: string format: cuid2 root: type: object required: - type - contactAttributeKey properties: type: type: string enum: - attribute contactAttributeKey: type: string description: A contact-attribute key defined in the workspace (e.g. `plan`, `role`). additionalProperties: false qualifier: type: object required: - operator properties: operator: type: string enum: - lessThan - lessEqual - greaterThan - greaterEqual - equals - notEquals - isSet - isNotSet - contains - doesNotContain - startsWith - endsWith - isOlderThan - isNewerThan - isBefore - isAfter - isBetween - isSameDay additionalProperties: false value: $ref: '#/components/schemas/SegmentFilterValue' additionalProperties: false - title: PersonFilter description: Matches on a built-in person identifier. type: object required: - id - root - qualifier - value properties: id: type: string format: cuid2 root: type: object required: - type - personIdentifier properties: type: type: string enum: - person personIdentifier: type: string description: A built-in person identifier (e.g. `userId`). additionalProperties: false qualifier: type: object required: - operator properties: operator: type: string enum: - equals - notEquals - isSet - isNotSet - contains - doesNotContain - startsWith - endsWith additionalProperties: false value: $ref: '#/components/schemas/SegmentFilterValue' additionalProperties: false - title: SegmentFilter description: Matches on membership in another segment. type: object required: - id - root - qualifier - value properties: id: type: string format: cuid2 root: type: object required: - type - segmentId properties: type: type: string enum: - segment segmentId: type: string description: Id of the segment to test membership against. additionalProperties: false qualifier: type: object required: - operator properties: operator: type: string enum: - userIsIn - userIsNotIn additionalProperties: false value: $ref: '#/components/schemas/SegmentFilterValue' additionalProperties: false - title: DeviceFilter description: Matches on the contact's device type. type: object required: - id - root - qualifier - value properties: id: type: string format: cuid2 root: type: object required: - type - deviceType properties: type: type: string enum: - device deviceType: type: string description: Device type (e.g. `desktop`, `phone`). additionalProperties: false qualifier: type: object required: - operator properties: operator: type: string enum: - equals - notEquals additionalProperties: false value: $ref: '#/components/schemas/SegmentFilterValue' additionalProperties: false SegmentFilters: type: array description: | Contact targeting filter tree. An empty array targets everyone. Each node joins the previous one via its `connector` (`and`/`or`; the first node's connector is null). A node's `resource` is either a single filter condition or a nested filter group (enabling parenthesized `and`/`or` logic). items: type: object required: - id - connector - resource properties: id: type: string format: cuid2 connector: type: - string - 'null' enum: - and - or - null description: Logical connector joining this node to the previous one. Null for the first node. resource: oneOf: - $ref: '#/components/schemas/SegmentFilter' - $ref: '#/components/schemas/SegmentFilters' description: A single filter condition, or a nested filter group for grouped logic. additionalProperties: false SurveyTargeting: type: object description: | App-survey contact targeting. Only valid for `type: app`. `filters: []` targets everyone. Setting or changing non-empty filters requires the Contacts entitlement; otherwise create/patch returns 403. `attribute` filters reference workspace contact-attribute keys (discover them via `GET /api/v3/contact-attribute-keys`) and `segment` filters reference other segment ids — both are workspace-scoped resources you must obtain before constructing filters (an invalid key/id targets no one rather than erroring). See `SegmentFilter` for the per-`root.type` operator sets. required: - filters properties: filters: $ref: '#/components/schemas/SegmentFilters' additionalProperties: false CreateSurveyRequest: type: object description: | Strict v3 survey creation document. This endpoint accepts survey structure only: name, metadata, languages, welcome card, blocks/elements/logic, endings, hidden fields, and variables. App surveys (`type: app`) additionally accept the `distribution` (display options + triggers) and `targeting` (contact segment filters) objects; these are rejected for `link` surveys. It rejects legacy `questions` and out-of-scope settings such as styling, follow-ups, recaptcha, single-use/email verification, slug, custom scripts, analytics fields, timestamps, and `createdBy`. Translatable fields use real locale-code maps. The map must include the canonical `defaultLanguage` key, such as `en-US`, so the server can persist the internal default translation. Locale keys must be canonical BCP 47 codes such as `de-DE`, `pt-PT`, or `zh-Hans-CN`. Non-default locale keys must be declared in `languages`; undeclared locale keys in metadata, welcome cards, blocks, or endings are rejected with `unsupported_locale`. `blocks[].id` and `variables[].id` may be omitted on create and will be generated by the server. Provide explicit cuid2 ids when other fields in the same request reference them. For normal sequential flow, omit `logicFallback`. It is only valid together with a non-empty `logic` array on the same block. required: - workspaceId - name - blocks properties: workspaceId: type: string format: cuid2 description: Workspace where the survey will be created. Requires read/write access. name: type: string minLength: 1 type: type: string enum: - link - app default: link description: | Survey type. `link` surveys are distributed by URL; `app` surveys are shown in-app and configure their runtime behavior via the `distribution` and `targeting` objects. `type` is immutable after creation (it cannot be changed via PATCH). status: type: string enum: - draft - inProgress - paused - completed default: draft metadata: allOf: - $ref: '#/components/schemas/SurveyMetadata' default: {} defaultLanguage: allOf: - $ref: '#/components/schemas/LocaleCode' default: en-US description: Default authoring locale. Translatable maps must include this locale key. languages: type: array default: [] items: $ref: '#/components/schemas/CreateSurveyLanguage' description: | Optional survey language configuration. Every non-default locale used by translatable maps must be declared here; omitted languages are not inferred from map keys. welcomeCard: allOf: - $ref: '#/components/schemas/SurveyWelcomeCard' default: enabled: false blocks: type: array minItems: 1 items: $ref: '#/components/schemas/CreateSurveyBlock' endings: type: array default: [] items: $ref: '#/components/schemas/SurveyEnding' hiddenFields: allOf: - $ref: '#/components/schemas/SurveyHiddenFields' default: enabled: false variables: type: array default: [] items: $ref: '#/components/schemas/CreateSurveyVariable' distribution: allOf: - $ref: '#/components/schemas/SurveyDistribution' description: App-survey runtime/display settings. Only valid when `type` is `app`. targeting: allOf: - $ref: '#/components/schemas/SurveyTargeting' description: App-survey contact targeting. Only valid when `type` is `app`. additionalProperties: false SurveyLanguage: type: object description: | Language configured for this survey. GET responses expose the server-emitted code/tag used as the translatable map key. Existing surveys can use region-qualified, language-only, or script-only codes. Disabled languages can still be read by the management API so unfinished translations can be completed. required: - code - default - enabled properties: code: type: string description: Server-emitted survey language code/tag used as the translatable map key. example: en-US alias: type: - string - 'null' description: Optional configured alias accepted by `?lang` for compatibility and agent discovery. example: english default: type: boolean description: Whether this is the default authoring language. enabled: type: boolean description: Whether this language is enabled for respondent-facing delivery. SurveyBlock: type: object description: | Block-based survey section. Block ids are stable public identifiers. Logic and fallbacks can jump to block ids or ending ids, so clients and agents should preserve ids unless intentionally creating/deleting a block. required: - id - name - elements properties: id: type: string format: cuid2 description: Stable block id. name: type: string minLength: 1 elements: type: array minItems: 1 items: $ref: '#/components/schemas/SurveyElement' logic: type: array items: $ref: '#/components/schemas/SurveyBlockLogic' logicFallback: type: string format: cuid2 description: | Block or ending id used when no logic condition matches. Only valid when this same block has at least one `logic` rule; omit it for normal sequential flow. buttonLabel: $ref: '#/components/schemas/TranslatableText' backButtonLabel: $ref: '#/components/schemas/TranslatableText' additionalProperties: false SurveyNumberVariable: type: object description: | Number variable. Used by `calculate` logic actions with numeric operators such as `add`, `subtract`, `multiply`, `divide`, or `assign`. required: - id - name - type - value properties: id: type: string format: cuid2 description: Stable variable id referenced from logic. name: type: string pattern: ^[a-z0-9_]+$ description: Unique variable name. Lowercase letters, numbers, and underscores only. type: type: string enum: - number value: type: number description: Default numeric value. additionalProperties: false SurveyTextVariable: type: object description: | Text variable. Used by `calculate` logic actions with text operators such as `assign` or `concat`. required: - id - name - type - value properties: id: type: string format: cuid2 description: Stable variable id referenced from logic. name: type: string pattern: ^[a-z0-9_]+$ description: Unique variable name. Lowercase letters, numbers, and underscores only. type: type: string enum: - text value: type: string description: Default text value. additionalProperties: false SurveyVariable: oneOf: - $ref: '#/components/schemas/SurveyNumberVariable' - $ref: '#/components/schemas/SurveyTextVariable' description: | Survey variable. Variable ids are stable references used by logic and calculation actions. Variable names are human-readable labels and must be unique within the survey. SurveyResource: type: object required: - id - workspaceId - createdAt - updatedAt - archivedAt - name - type - status - metadata - defaultLanguage - languages - welcomeCard - blocks - endings - hiddenFields - variables properties: id: type: string workspaceId: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time archivedAt: type: - string - 'null' format: date-time description: Soft-delete/archive marker (ISO 8601); `null` when the survey is active. name: type: string type: type: string enum: - link - app - website - web status: type: string enum: - draft - inProgress - paused - completed metadata: anyOf: - $ref: '#/components/schemas/SurveyMetadata' - type: 'null' defaultLanguage: type: string description: Emitted language code/tag for the survey default language. The internal `default` translation key is never exposed. languages: type: array items: $ref: '#/components/schemas/SurveyLanguage' welcomeCard: $ref: '#/components/schemas/SurveyWelcomeCard' blocks: type: array items: $ref: '#/components/schemas/SurveyBlock' endings: type: array items: $ref: '#/components/schemas/SurveyEnding' hiddenFields: $ref: '#/components/schemas/SurveyHiddenFields' variables: type: array items: $ref: '#/components/schemas/SurveyVariable' distribution: allOf: - $ref: '#/components/schemas/SurveyDistribution' description: App-survey runtime/display settings. Present only for app surveys; omitted for link surveys. targeting: allOf: - $ref: '#/components/schemas/SurveyTargeting' description: App-survey contact targeting. Present only for app surveys; omitted for link surveys. AISurveyGenerationLocaleCode: type: string enum: - de-DE - en-US - es-ES - fr-FR - hu-HU - ja-JP - nl-NL - pt-BR - pt-PT - ro-RO - ru-RU - sv-SE - tr-TR - zh-Hans-CN - zh-Hant-TW description: Supported app locale code that AI survey creation can return. example: en-US GenerateSurveyRequest: type: object description: | AI survey creation request. `prompt` is used only to create a draft payload; it is not persisted by this endpoint. The generated payload is validated and returned to the caller for a separate create request. required: - workspaceId - prompt properties: workspaceId: type: string format: cuid2 description: Workspace where the AI-created survey draft would be created. Requires read/write access. prompt: type: string minLength: 4 maxLength: 1200 description: | Natural-language survey goal and audience. Prompts with fewer than 24 characters or fewer than four whitespace-separated words return prompt feedback before any AI provider call. type: type: string enum: - link - app default: link description: Survey type to generate. The AI generates the survey content (blocks/questions) for both types; for `app` it additionally seeds a default `distribution` (display once, no triggers, no targeting) that you finish configuring before publishing. language: $ref: '#/components/schemas/AISurveyGenerationLocaleCode' description: | Preferred survey language. Session clients should send the logged-in user's language. The generator uses it when the prompt language is ambiguous or cannot be confidently matched to a supported app locale. additionalProperties: false GeneratedSurveyValidationLanguage: type: object required: - code - default - enabled properties: code: $ref: '#/components/schemas/AISurveyGenerationLocaleCode' default: type: boolean enabled: type: boolean additionalProperties: false GeneratedSurveyValidationResult: type: object required: - valid - invalid_params - languages properties: valid: type: boolean description: Whether the generated create payload passed the same v3 create preparation pipeline. invalid_params: type: array description: Repairable validation errors. Successful generate responses return an empty array. items: $ref: '#/components/schemas/InvalidParam' languages: type: array description: Languages that the generated create payload would connect or create. items: $ref: '#/components/schemas/GeneratedSurveyValidationLanguage' additionalProperties: false GenerateSurveyResult: type: object required: - language - payload - validation properties: language: $ref: '#/components/schemas/AISurveyGenerationLocaleCode' description: Supported app locale used for the AI-created survey content and create payload language maps. payload: allOf: - $ref: '#/components/schemas/CreateSurveyRequest' description: | Strict create payload ready for `POST /api/v3/surveys`. It always has `status: draft`. validation: $ref: '#/components/schemas/GeneratedSurveyValidationResult' additionalProperties: false ValidateSurveyCreateRequest: type: object required: - operation - data properties: operation: type: string enum: - create data: description: Intended create payload. Invalid documents are allowed so the endpoint can return repairable validation errors. anyOf: - $ref: '#/components/schemas/CreateSurveyRequest' - type: object additionalProperties: true additionalProperties: false PatchSurveyRequest: type: object minProperties: 1 description: | Patch payload shape. Top-level fields are partial; any provided nested object or array fully replaces that subtree. Omitted top-level fields are preserved. Immutable/system fields such as `id`, `workspaceId`, `type`, `defaultLanguage`, timestamps, `questions`, analytics, styling, and scripts are rejected. App surveys (stored `type: app`) additionally accept `distribution` and `targeting`; both are rejected for link surveys, and changing `targeting` filters requires the Contacts entitlement. `metadata`, `languages`, `welcomeCard`, `blocks`, `endings`, `hiddenFields`, and `variables` replace their full top-level value when provided. Missing ids in replaced arrays are deletions. Non-default locale keys in translatable maps must be declared in `languages`; undeclared locale keys are rejected with `unsupported_locale`. To remove a language, remove it from both `languages` and every translatable map that contains that locale key. Use `/api/v3/surveys/validate` with `operation: patch` before committing automated or agentic changes. properties: name: type: string minLength: 1 status: type: string enum: - draft - inProgress - paused - completed metadata: allOf: - $ref: '#/components/schemas/SurveyMetadata' languages: type: array items: $ref: '#/components/schemas/CreateSurveyLanguage' description: | Replaces the survey language configuration. The default language cannot be changed through PATCH; any `default: true` entry must match the existing default language. Languages referenced by `metadata`, `welcomeCard`, `blocks`, or `endings` translatable maps must be explicitly declared here, so remove the locale key from those maps as well when intentionally removing a language. welcomeCard: $ref: '#/components/schemas/SurveyWelcomeCard' blocks: type: array minItems: 1 items: $ref: '#/components/schemas/SurveyBlock' endings: type: array items: $ref: '#/components/schemas/SurveyEnding' hiddenFields: $ref: '#/components/schemas/SurveyHiddenFields' variables: type: array items: $ref: '#/components/schemas/SurveyVariable' distribution: allOf: - $ref: '#/components/schemas/SurveyDistribution' description: | App-survey runtime/display settings. Only valid for stored `type: app`. Replaces the full distribution; omitted scalars reset to their defaults. targeting: allOf: - $ref: '#/components/schemas/SurveyTargeting' description: App-survey contact targeting. Only valid for stored app surveys. additionalProperties: false ValidateSurveyPatchRequest: type: object required: - operation - surveyId - data properties: operation: type: string enum: - patch surveyId: type: string format: cuid2 data: description: Intended patch payload. Invalid documents are allowed so the endpoint can return repairable validation errors. anyOf: - $ref: '#/components/schemas/PatchSurveyRequest' - type: object additionalProperties: true additionalProperties: false ValidateSurveyRequest: oneOf: - $ref: '#/components/schemas/ValidateSurveyCreateRequest' - $ref: '#/components/schemas/ValidateSurveyPatchRequest' discriminator: propertyName: operation mapping: create: '#/components/schemas/ValidateSurveyCreateRequest' patch: '#/components/schemas/ValidateSurveyPatchRequest' SurveyValidationLanguage: type: object required: - code - default - enabled - writeBehavior properties: code: $ref: '#/components/schemas/LocaleCode' default: type: boolean enabled: type: boolean writeBehavior: type: string enum: - connect_or_create description: Validation is read-only; a real write would connect an existing language or create it if missing. additionalProperties: false SurveyValidationResult: type: object required: - valid - operation - invalid_params properties: valid: type: boolean description: Whether the submitted survey document is valid. operation: type: string enum: - create - patch invalid_params: type: array description: Repairable validation errors using document-relative paths. items: $ref: '#/components/schemas/InvalidParam' languages: type: array description: Languages that a successful write would connect or create. Present only when `valid=true`. items: $ref: '#/components/schemas/SurveyValidationLanguage' additionalProperties: false WorkflowStatus: type: string enum: - draft - enabled - disabled - archived description: | Workflow lifecycle state. `draft` workflows are editable and inert. `enabled` workflows respond to trigger events. `disabled` workflows keep their configuration but are inert. `archived` workflows are soft-deleted: read-only, excluded from default list reads, and restorable via unarchive. Status only changes through lifecycle endpoints. Valid transitions: `draft` → `enabled`, `enabled` ↔ `disabled`, `enabled`/`disabled` → `draft`, any non-archived state → `archived` (archive), and `archived` → `draft` (unarchive). WorkflowRunStatus: type: string enum: - queued - running - completed - failed - canceled description: | Workflow run lifecycle state. `completed`, `failed`, and `canceled` are terminal. Valid transitions: `queued` → `running`, `running` → `queued` (retry/backoff), `queued` → `canceled`, and `running` → `completed`/`failed`/`canceled`. Retries do not introduce a separate status; retry state is exposed via `attempt` and `nextAttemptAt`. WorkflowRunSummary: type: object description: | Slim run shape embedded as `lastRun` in workflow resources. The runs list (`GET /api/v3/workflows/runs`) returns `WorkflowRunListItem`, which extends this with `workflowName`. Excludes `triggerPayload`, `data`, and `logs`. required: - id - workflowId - workspaceId - workflowVersionId - status - isDryRun - triggerType - surveyId - responseId - error - attempt - createdAt - updatedAt - startedAt - finishedAt properties: id: type: string format: cuid2 workflowId: type: string format: cuid2 workspaceId: type: string format: cuid2 workflowVersionId: type: - string - 'null' format: cuid2 description: | Immutable workflow version snapshot the run executes against. `null` for dry runs of workflows that have never been enabled. Version contents are not readable through the v3 API in Scope 1. status: $ref: '#/components/schemas/WorkflowRunStatus' isDryRun: type: boolean description: '`true` for runs created by `POST /api/v3/workflows/{workflowId}/test`.' triggerType: type: string enum: - response.completed surveyId: type: - string - 'null' format: cuid2 responseId: type: - string - 'null' format: cuid2 description: Survey response that triggered the run. `null` for synthesized dry-run data or when the response was deleted. error: type: - string - 'null' description: Terminal or most recent failure reason. `null` while the run is healthy. attempt: type: integer minimum: 0 description: Retry attempt counter. `0` on the first execution; retries increment it without changing `status`. createdAt: type: string format: date-time updatedAt: type: string format: date-time startedAt: type: - string - 'null' format: date-time finishedAt: type: - string - 'null' format: date-time WorkflowListItem: type: object description: | Slim workflow shape returned by `GET /api/v3/workflows`. `triggerType` and `surveyId` are derived read-only projections of `definition.trigger`, so list consumers can render and filter without loading full definitions. Serialized dates are ISO 8601 strings. required: - id - workspaceId - name - description - status - triggerType - surveyId - createdBy - creator - createdAt - updatedAt - lastRun - runCount properties: id: type: string format: cuid2 workspaceId: type: string format: cuid2 name: type: string description: type: - string - 'null' status: $ref: '#/components/schemas/WorkflowStatus' triggerType: type: string enum: - response.completed description: Derived from `definition.trigger.triggerType`. surveyId: type: string format: cuid2 description: Derived from `definition.trigger.config.surveyId`. createdBy: type: - string - 'null' format: cuid2 description: User id that created the workflow. `null` when the creating user was deleted. creator: type: - object - 'null' required: - name properties: name: type: string description: Creating user's name. `null` when the creating user was deleted. createdAt: type: string format: date-time updatedAt: type: string format: date-time lastRun: anyOf: - $ref: '#/components/schemas/WorkflowRunSummary' - type: 'null' description: Most recent run summary (dry runs included). `null` when the workflow has never run. runCount: type: integer minimum: 0 description: Total number of runs (dry runs included) for this workflow. CursorPaginationMeta: type: object required: - limit - nextCursor properties: limit: type: integer minimum: 1 maximum: 100 nextCursor: type: - string - 'null' description: Opaque cursor for the next page. `null` when there are no more results. additionalProperties: false WorkflowNodeUi: type: object description: Builder-only UI metadata that does not affect workflow execution. Additional keys are preserved. properties: position: type: object required: - x - 'y' properties: x: type: number 'y': type: number additionalProperties: false collapsed: type: boolean additionalProperties: true WorkflowResponseCompletedTriggerNode: type: object required: - id - type - triggerType - config properties: id: type: string minLength: 1 type: type: string enum: - trigger triggerType: type: string enum: - response.completed label: type: string minLength: 1 maxLength: 120 description: Optional display label shown in the builder. config: type: object required: - surveyId properties: surveyId: type: string format: cuid2 description: Survey whose completed responses trigger the workflow. Must exist in the workspace. endingCardIds: type: array items: type: string format: cuid2 default: [] description: Ending card ids that should trigger the workflow. Empty or omitted means every ending matches. additionalProperties: false ui: $ref: '#/components/schemas/WorkflowNodeUi' additionalProperties: false WorkflowSendEmailActionConfig: type: object description: |- Send-email action with 1:1 field parity to survey Follow-ups (`ZSurveyFollowUpAction`). `to`, `subject` and `body` accept an empty string on create and update, so an author can save an unfinished draft. Completeness is enforced when the workflow has to run: enabling it returns **422** `workflow_not_executable` naming each blank field, and the dry run reports them as problems. required: - to - from - replyTo - subject - body - attachResponseData properties: to: type: string description: | Recipient: either a literal email address (e.g. a teammate) or the element id of a survey question / hidden field whose answer contains the respondent's email address (contact-info elements resolve index [2]) — the same semantics as the Follow-ups `to` field. A **literal** address must belong to someone who can access this workspace — an organization owner or manager, or a member of a team the workspace is shared with. Enabling a workflow whose literal recipient cannot access the workspace returns **422** `workflow_not_executable`, the dry run reports a `recipient_not_allowed` problem, and the runner refuses to send to it — so a workflow cannot forward response data to an arbitrary external inbox. An element-id `to` resolves to the respondent's own address and is never allowlist-checked. from: type: string format: email replyTo: type: array items: type: string format: email subject: type: string maxLength: 998 description: | Email subject. Used verbatim (recall tokens are not expanded in the subject), the same as Follow-ups. Capped at RFC 5322's maximum line length. body: type: string maxLength: 100000 description: | Email body as HTML with recall tokens (`#recall:[elementId]/fallback:x#`). Recall tokens are expanded against the response, the result is sanitized to a narrow HTML allowlist, and it is wrapped in the branded Follow-ups email template — identical rendering to survey Follow-ups. attachResponseData: type: boolean includeVariables: type: boolean default: false includeHiddenFields: type: boolean default: false additionalProperties: false WorkflowSendEmailActionNode: type: object description: 'Send-email action node. Renders 1:1 with survey Follow-ups: recall-token body, question/hidden-field `to` resolution, and the branded Follow-ups email template.' required: - id - type - actionType - config properties: id: type: string minLength: 1 type: type: string enum: - action actionType: type: string enum: - send_email label: type: string minLength: 1 maxLength: 120 description: Optional display label shown in the builder. config: $ref: '#/components/schemas/WorkflowSendEmailActionConfig' ui: $ref: '#/components/schemas/WorkflowNodeUi' additionalProperties: false WorkflowEdge: type: object required: - id - source - target properties: id: type: string minLength: 1 source: type: string minLength: 1 description: Node id the edge starts from. target: type: string minLength: 1 description: Node id the edge points to. sourceHandle: type: string minLength: 1 description: Builder handle identifier on the source node. Reserved for branch nodes (`then`/`else`) in future scopes. targetHandle: type: string minLength: 1 description: Builder handle identifier on the target node. additionalProperties: false WorkflowDefinition: type: object description: | Scope 1 workflow JSON document, mirroring `ZWorkflowDefinition` in `packages/workflows`. The definition is the public source of truth for the builder and API clients. Scope 1 intentionally excludes user-authored if/else branches, webhook actions, schedules, delays, loops, and AI/MCP-assisted creation; future node types extend `nodes` additively. Graph rules enforced on every write (**400** with `invalid_params` on violation): node ids are unique across `trigger` and `nodes`; every edge references existing node ids; the trigger has at most one outgoing edge; `entryNodeId` equals `trigger.id`. Trigger-only drafts (no `nodes`, no `edges`) are valid persisted documents. Executability rules (exactly one outgoing trigger edge, acyclic graph, every node reachable from the trigger) are additionally enforced by `enable` and `test` with **422** `workflow_not_executable`. required: - trigger - entryNodeId properties: schemaVersion: type: integer enum: - 1 default: 1 description: Definition schema version. Optional on writes (defaults to the current version); always present in responses. trigger: $ref: '#/components/schemas/WorkflowResponseCompletedTriggerNode' nodes: type: array default: [] items: $ref: '#/components/schemas/WorkflowSendEmailActionNode' description: Child nodes of the graph. The trigger lives in `trigger`, not in this array. Scope 1 accepts `send_email` action nodes only. Optional on writes (defaults to empty for trigger-only drafts); always present in responses. edges: type: array default: [] items: $ref: '#/components/schemas/WorkflowEdge' description: Optional on writes (defaults to empty for trigger-only drafts); always present in responses. entryNodeId: type: string minLength: 1 description: Workflow entry point. Must equal `trigger.id`. additionalProperties: false CreateWorkflowRequest: type: object required: - workspaceId - name - definition properties: workspaceId: type: string format: cuid2 name: type: string minLength: 1 maxLength: 120 description: type: - string - 'null' maxLength: 500 status: type: string enum: - draft description: New workflows are created as drafts in Scope 1. definition: $ref: '#/components/schemas/WorkflowDefinition' additionalProperties: false WorkflowResource: description: | Full workflow shape returned by detail, create, update, duplicate, and lifecycle endpoints: the list-item fields plus the complete `definition` document. allOf: - $ref: '#/components/schemas/WorkflowListItem' - type: object required: - definition properties: definition: $ref: '#/components/schemas/WorkflowDefinition' WorkflowRunListItem: description: | Run-list row returned by `GET /api/v3/workflows/runs`: the slim run summary plus the resolved `workflowName`, joined so the workspace-wide runs table can label each row. Distinct from the `lastRun` summary embedded in workflow resources, which omits the name (the parent workflow already carries it). Excludes `triggerPayload`, `data`, and `logs`. allOf: - $ref: '#/components/schemas/WorkflowRunSummary' - type: object required: - workflowName properties: workflowName: type: string description: Name of the workflow this run belongs to, joined for the runs-list display. WorkflowTriggerPayload: type: object description: | Trigger payload snapshot captured when a run is created, mirroring `ZWorkflowTriggerRunPayload`. Additional runner keys may be present. required: - type - workspaceId - surveyId - responseId - triggeredAt properties: type: type: string enum: - response.completed workspaceId: type: string format: cuid2 surveyId: type: string format: cuid2 responseId: type: string format: cuid2 description: For dry runs without a `responseId` input, this references the synthesized sample response. endingCardId: type: string format: cuid2 description: Ending card the respondent reached, when the survey uses ending cards. data: type: object description: Response data snapshot used by the runner. triggeredAt: type: string format: date-time additionalProperties: true WorkflowRunLogStatus: type: string enum: - pending - running - succeeded - failed - skipped description: Status of one executed (or skipped) workflow step within a run. WorkflowRunStepResult: type: object description: In-document result for one workflow step, mirroring `ZWorkflowStepResult`. required: - stepId - stepType - status properties: stepId: type: string minLength: 1 description: Node id of the executed step. stepType: type: string minLength: 1 description: Node/step type, e.g. `response.completed` or `send_email`. status: $ref: '#/components/schemas/WorkflowRunLogStatus' input: type: object description: Step input snapshot. output: type: object description: Step output snapshot, or the mocked output for dry runs. error: type: string startedAt: type: string format: date-time finishedAt: type: string format: date-time additionalProperties: false WorkflowRunData: type: object description: | Run execution data persisted with the run, mirroring `ZWorkflowRunData`. Scope 1 exposes this as a debug-oriented JSON dump. Additional runner keys may be present. required: - steps properties: trigger: allOf: - $ref: '#/components/schemas/WorkflowTriggerPayload' description: Optional duplicate of the trigger payload snapshot kept inside the run data document. steps: type: array items: $ref: '#/components/schemas/WorkflowRunStepResult' additionalProperties: true WorkflowRunLog: type: object description: Persisted trace entry for one workflow run step, mirroring the `WorkflowRunLog` table row. required: - id - runId - sequence - stepId - stepType - status - input - output - error - startedAt - finishedAt properties: id: type: string format: cuid2 runId: type: string format: cuid2 sequence: type: integer minimum: 0 description: Order of the entry within the run. stepId: type: string minLength: 1 stepType: type: string minLength: 1 status: $ref: '#/components/schemas/WorkflowRunLogStatus' input: type: object description: Step input snapshot. Empty object when nothing was recorded. output: type: object description: Step output snapshot (mocked for dry runs). Empty object when nothing was recorded. error: type: - string - 'null' startedAt: type: - string - 'null' format: date-time finishedAt: type: - string - 'null' format: date-time additionalProperties: false WorkflowRunResource: description: | Full run shape returned by `GET /api/v3/workflows/runs/{runId}` and `POST /api/v3/workflows/{workflowId}/test`: the summary fields plus the captured trigger payload, per-step results, the persisted log trace, and retry/idempotency metadata. allOf: - $ref: '#/components/schemas/WorkflowRunSummary' - type: object required: - triggerPayload - data - logs - idempotencyKey - nextAttemptAt - lastErrorAt properties: triggerPayload: $ref: '#/components/schemas/WorkflowTriggerPayload' data: $ref: '#/components/schemas/WorkflowRunData' logs: type: array items: $ref: '#/components/schemas/WorkflowRunLog' description: Persisted step-by-step trace, ordered by `sequence`. Empty while the run is still `queued`. idempotencyKey: type: - string - 'null' description: Deduplication key for this run, unique per workflow. Set from the `Idempotency-Key` header on dry runs or from trigger-event deduplication. nextAttemptAt: type: - string - 'null' format: date-time description: When the next retry attempt is scheduled. `null` when no retry is pending. lastErrorAt: type: - string - 'null' format: date-time PatchWorkflowRequest: type: object description: | Partial workflow update. `name` and `description` are accepted in any state except `archived`; `definition` is only accepted while the workflow is `draft` or `disabled`. `status` is intentionally not patchable — use the lifecycle endpoints. minProperties: 1 properties: name: type: string minLength: 1 maxLength: 120 description: type: - string - 'null' maxLength: 500 definition: $ref: '#/components/schemas/WorkflowDefinition' additionalProperties: false DuplicateWorkflowRequest: type: object properties: name: type: string minLength: 1 maxLength: 120 description: Optional name for the duplicate. If omitted, the server chooses a non-conflicting copy name. additionalProperties: false WorkflowTestResult: type: object description: | Result of dry-running (testing) a workflow: whether it would execute and, if not, why not. No run is created and no side effects occur. required: - workflowId - ok - problems properties: workflowId: type: string format: cuid2 example: clworkflow0000000000000000 ok: type: boolean description: | True when the definition is executable, every trigger reference resolves, and every literal `send_email` recipient can access this workspace. problems: type: array description: Every problem found, so all issues can be fixed at once. Empty when `ok` is true. items: type: object required: - code - field - message properties: code: type: string enum: - definition_not_executable - survey_not_found - ending_card_not_found - recipient_not_allowed description: Machine-readable problem category. field: type: string description: | Dotted path to the offending field, e.g. `definition.trigger.config.surveyId`. message: type: string description: Human-readable explanation of the problem. ActionClassResource: type: object description: A workspace action class that can be referenced from an app-survey trigger. required: - id - name - description - type - key properties: id: type: string format: cuid2 description: Use this value as `distribution.triggers[].actionClassId` on an app survey. name: type: string description: type: - string - 'null' type: type: string enum: - code - noCode key: type: - string - 'null' description: Code-action key for code-type action classes; null for no-code actions. additionalProperties: false ContactAttributeKeyResource: type: object description: | A workspace contact-attribute key. Use `key` as `targeting.filters[].root.contactAttributeKey` on an app survey; `dataType` indicates which operators apply (e.g. `number`/`date` attributes support arithmetic/date operators). required: - id - key - name - description - type - dataType properties: id: type: string format: cuid2 key: type: string description: The attribute key used in targeting filters (e.g. `plan`, `role`). name: type: - string - 'null' description: Human-readable display name, or null. description: type: - string - 'null' type: type: string enum: - default - custom description: Whether this is a built-in (`default`) or workspace-defined (`custom`) attribute. dataType: type: string enum: - string - number - date additionalProperties: false TagResource: type: object required: - id - name - workspaceId - createdAt - updatedAt - count properties: id: type: string format: cuid2 name: type: string description: Tag label as shown in the app. Unique within a workspace. workspaceId: type: string format: cuid2 createdAt: type: string format: date-time updatedAt: type: string format: date-time count: type: integer minimum: 0 description: Responses currently carrying this tag. `0` when the tag exists but has never been applied. responses: V3BadRequest: description: Bad Request — malformed JSON, invalid query/body/params, duplicate name, or unsupported field. 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: $ref: '#/components/schemas/Problem' V3Unauthorized: description: Not authenticated (no valid session or API key). 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: $ref: '#/components/schemas/Problem' V3Forbidden: 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: $ref: '#/components/schemas/Problem' V3TooManyRequests: 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: $ref: '#/components/schemas/Problem' V3InternalServerError: 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: $ref: '#/components/schemas/Problem' V3UnprocessableContent: description: Unprocessable Content — semantic validation failed or the requested lifecycle transition is invalid. 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: $ref: '#/components/schemas/Problem' WorkflowResourceResponse: description: Workflow returned 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: $ref: '#/components/schemas/WorkflowResource' V3Conflict: description: Conflict — an `Idempotency-Key` retry arrived while the original request was still being processed (IETF `draft-ietf-httpapi-idempotency-key-header` semantics). Retry after the original request completes to receive its stored result. 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: $ref: '#/components/schemas/Problem' parameters: WorkspaceIdQuery: in: query name: workspaceId required: true schema: type: string format: cuid2 description: Workspace identifier. This is the canonical container ID for v3 APIs. LimitQuery: in: query name: limit schema: type: integer minimum: 1 maximum: 100 default: 20 description: Page size (max 100). CursorQuery: in: query name: cursor schema: type: string description: Opaque cursor returned as `meta.nextCursor` from the previous page. Omit on the first request. WorkflowRunIdPath: in: path name: runId required: true schema: type: string format: cuid2 description: Workflow run identifier. WorkflowIdPath: in: path name: workflowId required: true schema: type: string format: cuid2 description: Workflow identifier.