{"record":{"id":"aa70b5b608eda47a","repo":"antiwork/gumroad","slug":"cannot-update-product-level-rich-content-while-in","errorCode":null,"errorMessage":"Cannot update product-level rich content while in per-variant mode. Set has_same_rich_content_for_all_variants to true first, or use the variant endpoint to update per-variant content.","messagePattern":"Cannot update product-level rich content while in per-variant mode\\. Set has_same_rich_content_for_all_variants to true first, or use the variant endpoint to update per-variant content\\.","errorType":"exception","errorClass":"Link::LinkInvalid","httpStatus":200,"severity":"error","filePath":"app/controllers/api/v2/links_controller.rb","lineNumber":506,"sourceCode":"          end\n\n          validate_file_embed_conflicts!(skip_variant_embeds: flag_changed && @product.has_same_rich_content_for_all_variants? && !@normalized_rich_content.nil?)\n\n          rich_content_params = build_rich_content_params\n          file_id_mappings = SaveFilesService.perform(@product, { files: @normalized_files }, rich_content_params) || {}\n        end\n\n        @product.save!\n\n        @product.save_tags!(params[:tags]) if params.key?(:tags)\n\n        if params.key?(:cover_ids)\n          cover_ids = normalize_params_recursively(params[:cover_ids])\n          @product.reorder_previews(cover_ids.map.with_index.to_h)\n        end\n\n        if !@normalized_rich_content.nil? && !@product.has_same_rich_content_for_all_variants? && @product.alive_variants.exists?\n          raise Link::LinkInvalid, \"Cannot update product-level rich content while in per-variant mode. Set has_same_rich_content_for_all_variants to true first, or use the variant endpoint to update per-variant content.\"\n        end\n\n        if flag_changed\n          if @normalized_rich_content.nil?\n            migrate_rich_content_for_flag_change!\n          else\n            clear_inactive_rich_content_side!\n            strip_upsell_ids_from_normalized_rich_content!\n          end\n        end\n\n        rich_content_or_flag_changed = !@normalized_rich_content.nil? || flag_changed\n\n        unless @normalized_rich_content.nil?\n          save_rich_content!\n        end\n\n        if rich_content_or_flag_changed","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/controllers/api/v2/links_controller.rb#L488-L524","documentation":"Link::LinkInvalid raised in Api::V2::LinksController#update when the request includes rich_content (product-level) but the product is in per-variant content mode (has_same_rich_content_for_all_variants is false) and alive variants exist. Product-level content and per-variant content are mutually exclusive states; the API refuses to write product-level pages that no variant would render, and tells you to flip the flag first or use the per-variant endpoint.","triggerScenarios":"PATCH /api/v2/links/:id with a rich_content key while the product has variants and has_same_rich_content_for_all_variants is false (previously switched to per-variant mode); the guard runs after the primary save, so it rejects the content-shape mismatch on the request boundary.","commonSituations":"Integration assumes every product takes product-level rich_content, but the seller had switched the product to per-variant; a template apply-script hitting variant-mode products; migration scripts that set rich_content before flipping the shared flag.","solutions":["If you want one shared content set: send has_same_rich_content_for_all_variants: true in the same (or a prior) request, then the rich_content write is accepted (see migrate_to_shared_rich_content! constraints).","If variants should keep distinct content: write to the variant endpoint (Api::V2::VarientsController-style per-variant rich_content update) instead of product-level.","In shared clients, GET the product first and branch on has_same_rich_content_for_all_variants before choosing which endpoint to call.","Don't ignore the flag and force product-level writes — you would clobber the per-variant content model."],"exampleFix":"# before: product-level rich_content on a per-variant product\nLinkApi.update(link_id, rich_content: pages)\n# after: flip to shared mode in the same payload\nLinkApi.update(link_id, has_same_rich_content_for_all_variants: true, rich_content: pages)","handlingStrategy":"validation","validationCode":"product = LinkApi.get(link_id)\nif product.has_same_rich_content_for_all_variants\n  LinkApi.update(link_id, rich_content: pages)\nelse\n  VariantApi.update(link_id, variant_id, rich_content: pages)\nend","typeGuard":"def product_level_rich_content_writable?(product)\n  product.has_same_rich_content_for_all_variants? || product.alive_variants.empty?\nend","tryCatchPattern":null,"preventionTips":["Branch on has_same_rich_content_for_all_variants before choosing the endpoint.","Flip the flag and the content in one request when switching modes.","Refetch the product before writes in long-lived editor sessions."],"tags":["api","rich-content","variants","products","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}