antiwork/gumroad · error · Link::LinkInvalid

Cannot update product-level rich content while in per-varian

Error message

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.

What it means

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.

Source

Thrown at app/controllers/api/v2/links_controller.rb:506

          end

          validate_file_embed_conflicts!(skip_variant_embeds: flag_changed && @product.has_same_rich_content_for_all_variants? && !@normalized_rich_content.nil?)

          rich_content_params = build_rich_content_params
          file_id_mappings = SaveFilesService.perform(@product, { files: @normalized_files }, rich_content_params) || {}
        end

        @product.save!

        @product.save_tags!(params[:tags]) if params.key?(:tags)

        if params.key?(:cover_ids)
          cover_ids = normalize_params_recursively(params[:cover_ids])
          @product.reorder_previews(cover_ids.map.with_index.to_h)
        end

        if !@normalized_rich_content.nil? && !@product.has_same_rich_content_for_all_variants? && @product.alive_variants.exists?
          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."
        end

        if flag_changed
          if @normalized_rich_content.nil?
            migrate_rich_content_for_flag_change!
          else
            clear_inactive_rich_content_side!
            strip_upsell_ids_from_normalized_rich_content!
          end
        end

        rich_content_or_flag_changed = !@normalized_rich_content.nil? || flag_changed

        unless @normalized_rich_content.nil?
          save_rich_content!
        end

        if rich_content_or_flag_changed

View on GitHub (pinned to afeacbd394)

Solutions

  1. 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).
  2. If variants should keep distinct content: write to the variant endpoint (Api::V2::VarientsController-style per-variant rich_content update) instead of product-level.
  3. In shared clients, GET the product first and branch on has_same_rich_content_for_all_variants before choosing which endpoint to call.
  4. Don't ignore the flag and force product-level writes — you would clobber the per-variant content model.

Example fix

# before: product-level rich_content on a per-variant product
LinkApi.update(link_id, rich_content: pages)
# after: flip to shared mode in the same payload
LinkApi.update(link_id, has_same_rich_content_for_all_variants: true, rich_content: pages)
Defensive patterns

Strategy: validation

Validate before calling

product = LinkApi.get(link_id)
if product.has_same_rich_content_for_all_variants
  LinkApi.update(link_id, rich_content: pages)
else
  VariantApi.update(link_id, variant_id, rich_content: pages)
end

Type guard

def product_level_rich_content_writable?(product)
  product.has_same_rich_content_for_all_variants? || product.alive_variants.empty?
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/aa70b5b608eda47a. Report an issue: GitHub.