---
title: "Update survey"
method: PATCH
path: "/surveys/{id}"
tags: ["Surveys"]
---

# Update survey

`PATCH /surveys/{id}`

## Request

- Base URL: `https://api.pickfu.com/v1`
- URL: `https://api.pickfu.com/v1/surveys/{id}`
- Auth: HTTP bearer

Updates a draft survey. All fields use replace-all semantics — for example, sending `tags` replaces all existing tags, and sending `questions` replaces all existing questions.

Only surveys in `unpublished` (draft) status can be updated. You can update the name, tags, project, questions, targeting, reporting, country, sample size, and survey intent.

## Path parameters

- `id` string, required

## Request body

- object
  - `name` string: Survey name (1-255 characters, trimmed)
  - `tags` union[]: Tags to replace all existing tags. Each item has either id or name.
    - union
      - object
        - `id` union, required: Existing tag ID — opaque identifier (tag_<32-hex>) or legacy integer
          - string
          - number
      - object
        - `name` string, required: Tag name (will find or create)
  - `projectId` union: Project GUID (string) or numeric ID to associate, or null to remove project. Mutually exclusive with `projects`.
    - string
    - number
  - `projects` union[]: Project GUIDs (or numeric IDs) to associate the survey with. Replace-all semantics. Mutually exclusive with `projectId`.
    - union
      - string
      - number
  - `questions` object[]: Questions to replace all existing questions (replace-all semantics)
    - `id` string: Question GUID from GET response. Include to update in-place (preserving response data). Omit to create as new.
    - `type` 'head_to_head' | 'ranked' | 'open_ended' | 'click_test' | 'emoji_rating' | 'star_rating' | 'five_second_test' | 'screen_recording' | 'single_select' | 'multi_select' | 'likert' | 'van_westendorp', required: Question type. Every type except screen_recording automatically collects a required written explanation from each respondent, returned as the response's `explanation` field. Do NOT add a separate open_ended question to capture 'why' — it duplicates the built-in explanation and wastes a question slot (multi-question surveys are priced per question).
    - `prompt` string, required: Question text prompt (max 255 chars). Every response automatically includes a required written explanation from the respondent for all types except screen_recording — phrase the real question here and PickFu collects the 'why' automatically.
    - `variant` string, nullable: Persisted question variant. For likert questions, accepts canonical variant slugs returned by GET.
    - `likertScale` 'agreement5' | 'agreement7' | 'frequency' | 'satisfaction': Likert scale preset. Only honored when type=likert. On PATCH, omitting this field leaves the existing scale unchanged. DEV-3128.
    - `context` object: Context provided with the question: text, a mediaUrl, or both (text is then the caption shown with the image, and mediaUrl must be an image URL). Also accepts the {type, value, text?} format from GET responses.
      - `text` string
      - `mediaUrl` string
      - `type` string
      - `value` string
    - `options` object[]: Options for the question
      - `id` string: Option GUID from GET response. Include to update in-place. Omit to create as new.
      - `value` string
      - `mediaUrl` string
      - `asin` string
      - `imageSet` union[]
        - union
          - string
          - object
            - `source` 'amazon' | 'walmart' | 'google_play' | 'apple', required
            - `id` string, required
      - `imageSetDirection` 'vertical' | 'horizontal' | 'a-plus'
      - `mockup` object, nullable: Either an Amazon product mockup (`type`, `image`, `title`, `price`, …) or a first-party mockup template (`template` with `values`, `import` or both), never both. A template keeps the option a text option: send `value` as well, which is the option's text and fills the template's display field. Every option of a question names the same template, or none does. List the templates and the values each takes with GET /v1/mockup-templates. On update, leaving `mockup` out keeps the option's page, and `null` removes the question's mockup template from every option.
        - `type` 'serp' | 'listing'
        - `image` string
        - `title` string
        - `numRatings` number
        - `reviewScore` number
        - `rating` number
        - `reviews` number
        - `price` number
        - `currency` string
        - `template` string: Lookup key of a mockup template from GET /v1/mockup-templates, such as `app-store-page`.
        - `values` object: The template's field values, keyed by field name, as its `valuesSchema` describes. Leave out the display field: it is the option's `value`.
        - `import` object: A store listing that fills the template's values before the survey is saved, for templates whose `importSources` list the store. `values` sent beside it win, so send the fields a store does not provide, such as an App Store subtitle, there. The option's `value` stays its text. Images are copied to PickFu's media CDN as when sent in `values`.
          - `source` 'apple', required
          - `id` string, required: The app: an App Store link such as https://apps.apple.com/us/app/name/id123456789, or the number after "id".
          - `country` string: Two-letter storefront to read the listing from when the link names none. Defaults to the survey's country.
  - `targeting` string[]: Targeting trait permalinks (replace-all semantics)
  - `reporting` string[]: Reporting trait permalinks (replace-all semantics)
  - `country` string: Two-letter country code (e.g. US)
  - `sampleSize` string: Number of respondents. Standard surveys: "15", "30", "50", "75", "100", "200", "300", "500". Screen recording surveys: "5", "10", "15", "20", "25", "30", "50", "75" (max 75).
  - `surveyIntent` string: Original intent of the survey as interpreted by AI (max 255 characters)
  - `externalId` string: Optional opaque correlation key for your own records. Omit when unset. Case-sensitive and byte-exact — send the identical string every time. Many surveys may share the same id. We do not interpret this value.
  - `template` object: Create only: sending it here is a 400. Edit a draft made from a template with `questions`, using the ids from GET /v1/surveys/{id}; its edits must stay within the template limits shown in `template.constraints`.

## Response `200`

Default Response

- object
  - `success` boolean
  - `data` object: Full survey data including questions, options, and metadata
    - `id` string: Unique survey GUID
    - `name` string, nullable: Survey name/title
    - `country` string: Two-letter country code (e.g. US, GB)
    - `currency` string: ISO 4217 currency derived from the survey target country
    - `sampleSize` number: Number of respondents requested
    - `numResponses` number, nullable: Number of responses collected so far
    - `status` string: Survey status: unpublished, in-progress, completed, or archived (deleted is also possible here for soft-deleted surveys, which the list endpoint excludes). Same vocabulary as the list endpoint — a survey always has the identical status string on both.
    - `targeting` string[]: Array of targeting trait permalinks applied to this survey
    - `reporting` string[]: Array of reporting trait permalinks for demographic breakdowns
    - `reportingDefaultsApplied` string[]: Reporting trait permalinks that were auto-injected as free defaults (based on plan level and country), not explicitly requested by the caller
    - `tags` object[]: Tags associated with the survey
      - `id` string, required: Opaque tag identifier (tag_<32-hex>)
      - `name` string, required: Tag name
    - `projects` object[]: Projects the survey belongs to
      - `id` string, required: Project GUID
      - `name` string, required: Project name
    - `surveyUrl` string: Public URL to view the survey results
    - `surveyIntent` string: AI-interpreted intent of the survey
    - `externalId` string: Optional opaque correlation key for your own records. Omitted when unset. Case-sensitive and byte-exact. Many surveys may share the same id. We do not interpret this value.
    - `template` object, nullable: The template the survey was made from and the limits it must meet to publish, or null. Edits that break a limit are rejected.
      - `id` string, nullable: Template id (cmb_...), or null when the template can no longer be addressed
      - `name` string
      - `constraints` object
        - `questionTypes` string[], required
        - `optionTypes` string[], required
        - `optionCount` object, required
          - `min` integer, required
          - `max` integer, required
        - `sampleSizes` string[], required
        - `defaultSampleSize` string, required
        - `countries` string[], required
        - `defaultCountry` string, required
    - `questions` object[]: Questions in the survey with options, responses, and AI metadata
      - `id` string: Question ID (GUID)
      - `type` string: Question type (head_to_head, ranked, open_ended, click_test, emoji_rating, star_rating, five_second_test, screen_recording, single_select, multi_select, likert, van_westendorp)
      - `variant` string, nullable: Question variant for specialized display modes
      - `value` string: Question text
      - `translation` string, nullable: Translated question text
      - `likertScale` 'agreement5' | 'agreement7' | 'frequency' | 'satisfaction' | 'null', nullable: Likert scale preset, returned for likert questions whose variant matches one of the canonical presets. Null for non-likert questions or legacy likert questions with no recognized variant.
      - `options` object[], nullable: Answer options for this question
        - `id` string: Option GUID for round-trip updates
        - `label` string: Option identifier label (e.g. "Option A")
        - `value` union: Option content: text string, Cloudinary URL, or array of URLs (image-set)
          - string
          - string[]
        - `type` string: Option data type: text, image, video, product, or image-set
        - `translation` string, nullable: Translated option text
        - `product` object, nullable: Product data for product-type options (ASIN or custom mockups)
          - `asin` string, nullable: Amazon ASIN (null for custom mockups)
          - `title` string: Product title
          - `price` number: Product price
          - `salePrice` number, nullable: Discounted/sale price. null when the product is not on sale.
          - `currency` string, nullable: Currency code for the price
          - `rating` number: Star rating (0-5)
          - `reviews` number: Number of reviews
          - `reviewScore` number, nullable: Alias of rating for public API round-trip
          - `numRatings` number, nullable: Alias of reviews for public API round-trip
          - `image` string: Product image URL
        - `imageSetDirection` string: Image stack layout direction: horizontal or vertical (image-set options only)
        - `mockupType` string: Product mockup rendering style: serp or listing (product options only)
        - `mockup` object: The mockup template a text option shows: `template` is its lookup key and `values` its stored field values, images at their PickFu media CDN copies. `values` leaves out the display field, which is the option's `value`. On update, leaving `mockup` out or sending it back unchanged keeps the page, and `mockup: null` removes the question's template.
          - `template` string
          - `values` object
      - `context` object, nullable: Additional context provided with the question (e.g. a product description or image)
        - `type` string: Context type (text or media)
        - `value` string: Context content: the text, or for media the hosted media URL
        - `text` string: Caption shown with the image. Present only on a media context that has one; send it back with the media URL to keep both.
        - `translation` string, nullable: Translated context text
      - `results` object, nullable: Aggregated, page-faithful summary of the outcome for this question — the same percentages and ranking shown on the results page. Null for question types without a simple per-option aggregate (open_ended, click_test, likert, five_second_test, screen_recording, round_robin) or before any responses arrive. For ranked questions, `votes` is the final instant-runoff tally the percentage is computed from, and differs from the first-choice count shown beside each option on the page; the winner exceeds 50%.
        - `totalResponses` number: Number of valid responses the percentages are computed against
        - `winningOptionId` string, nullable: GUID of the winning option, or null on a tie / before a winner is decided
        - `averageScore` number: Average rating, returned for rating questions only (emoji_rating, star_rating)
        - `options` object[]: Per-option results, ordered by rank (winner first)
          - `id` string: Option GUID
          - `label` string, nullable: Option label (e.g. "A")
          - `votes` number: Vote count; the final instant-runoff tally for ranked questions
          - `percentage` number: round(votes / totalResponses * 100), matching the results page
          - `rank` number, nullable: Final placement (1 = winner)
          - `isWinner` boolean: Whether this option is the winner
      - `responses` object[]: Respondent answers for this question
        - `id` string: Response ID
        - `pinned` boolean: Whether the response is pinned
        - `explanation` string, nullable: Written explanation from the respondent
        - `translation` string, nullable: Translated explanation text
        - `chosenOption` string, nullable: Label of the option chosen by the respondent
        - `selectedOptions` string[], nullable: Labels of options selected by the respondent for multi-select questions
        - `score` number, nullable: Rating score (for emoji_rating, star_rating question types)
        - `clicks` object[], nullable: Click coordinates for click_test questions
          - `number` number: Click sequence number
          - `xCoord` number: X coordinate of the click
          - `yCoord` number: Y coordinate of the click
        - `ranking` object[], nullable: Respondent's ranking of options for ranked question types. Each entry pairs a rank (1 = top pick) with the option label.
          - `rank` number: Rank position (1 = respondent's top pick)
          - `option` string, nullable: Option label at this rank
        - `media` string, nullable: Media URL (for screen_recording question types)
        - `mediaTranscription` string, nullable: Transcription of the recorded media
        - `matchup` object, nullable: Head-to-head matchup pairing shown to the respondent
          - `option1` string: First option in the matchup
          - `option2` string: Second option in the matchup
        - `statements` object[], nullable: Per-statement responses for likert question types. Each entry pairs a statement with the numeric value the respondent picked on the configured scale. DEV-3149.
          - `id` string: GUID of the statement (the question option)
          - `statement` string, nullable: Statement text
          - `value` number: Numeric value the respondent picked on the Likert scale (1..N for the configured scale)
        - `prices` object, nullable: Van Westendorp price-sensitivity responses. Four numeric prices the respondent provided for the four standardized PSM questions.
          - `tooCheap` number: Price at which the product would be so cheap that quality is questionable
          - `bargain` number: Price at which the product would be a bargain
          - `expensive` number: Price at which the product starts to feel expensive but is still worth considering
          - `tooExpensive` number: Price at which the product would be too expensive to consider
        - `respondent` object[]: Demographic traits of the respondent
          - `trait` string: Trait category name
          - `value` string: Trait value
      - `ai` object: AI-generated metadata such as summaries and key themes for this question
  - `cost` object: Cost information for the survey
    - `amount` number: Survey cost in dollars
    - `balance` number: Credit balance in dollars
    - `currency` string: Currency code (USD)
    - `canPayWithBalance` boolean: Whether balance covers the cost
    - `quotedCreditsCost` number: Post-discount cost in credits, after free credits applied (1 credit = $2 USD)
    - `creditsCostSubtotal` number: Pre-discount cost in credits (1 credit = $2 USD)
    - `discounts` array[]: Credit-side discount line items, each entry is [label, amountInCredits]. Amounts are denominated in credits so they subtract directly from creditsCostSubtotal
      - string[]
    - `discountsMoney` array[]: Dollar-side discount line items, each entry is [label, formattedDollarAmount] (e.g. "$20.08")
      - string[]
    - `planRequired` string, nullable: Plan tier required for current survey configuration, or null if the current plan is sufficient
    - `priceLines` array[]: The survey builder's price summary, each entry is [label, formattedDollarAmount] (e.g. ["Head-to-Head: Text Length (+1.2x)", "$10.00"]). Each amount is that line's cost for the whole sample: base price, extra options, text or video length, targeting and reporting. A $0.00 line is included free. The lines come before discounts: they add up to `creditsCostSubtotal` in dollars, and `discountsMoney` takes that down to `amount`
      - string[]
  - `completionEstimate` object: Estimated completion-time bucket for the current audience
    - `bucket` string: Completion-time bucket: SUPER_EASY (<2h), EASY (1-2 days), MEDIUM (up to 2 weeks), HARD (may take longer), MAY_NOT_COMPLETE
    - `estimatedMinutes` number, nullable: Estimated completion time in minutes, or null when unavailable
    - `status` string: ready when an estimate exists, pending while computing

## Other responses

- `400`: Default Response
- `401`: Default Response
- `403`: Default Response
- `404`: Default Response
- `409`: Default Response
- `422`: A request a template cannot satisfy: the first problem as `code` and `param`, and every problem in `violations`. A product combo template lists each limit with its allowed and received values; a mockup template lists each option field to change, as a path such as `questions[0].options[1].mockup.values.icon`.
- `428`: Default Response
- `429`: A request a template cannot satisfy: the first problem as `code` and `param`, and every problem in `violations`. A product combo template lists each limit with its allowed and received values; a mockup template lists each option field to change, as a path such as `questions[0].options[1].mockup.values.icon`.
- `503`: A request a template cannot satisfy: the first problem as `code` and `param`, and every problem in `violations`. A product combo template lists each limit with its allowed and received values; a mockup template lists each option field to change, as a path such as `questions[0].options[1].mockup.values.icon`.

---

[API](https://skmtc.dev/pickfu/apis/pickfu-api.md) · [All operations](https://skmtc.dev/pickfu/apis/pickfu-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pickfu/apis/pickfu-api/revisions/919fc58413ea?raw)
