{"openapi":"3.1.0","info":{"title":"Sign Customiser API Documentation","version":"1.0.0","x-sign-customiser-previous-generation":{"effective_at":"2026-07-22T00:00:00Z","deprecation":"@1784678400","sunset_at":"2027-01-22T00:00:00Z","sunset":"Fri, 22 Jan 2027 00:00:00 GMT","migration_url":"https://www.signcustomiser.com/help/api/guides/migration-v2-to-v3/"}},"servers":[{"url":"https://web.signcustomiser.com"}],"tags":[{"name":"Analytics"},{"name":"Custom integrations"},{"name":"Customiser backboards"},{"name":"Customiser backlights"},{"name":"Customiser colours"},{"name":"Customiser extras"},{"name":"Customiser fonts"},{"name":"Customiser font tiers"},{"name":"Customiser forms"},{"name":"Customiser icons"},{"name":"Customiser jackets"},{"name":"Customiser labels"},{"name":"Customiser languages"},{"name":"Customiser legacy fixed-height sizes"},{"name":"Customiser length pricings"},{"name":"Customiser letter parts"},{"name":"Customiser letter types"},{"name":"Customiser materials"},{"name":"Customiser mounting colours"},{"name":"Customiser mountings"},{"name":"Customiser presets"},{"name":"Customiser pricing"},{"name":"Customisers"},{"name":"Customiser settings"},{"name":"Customiser sizes"},{"name":"Customiser support finishes"},{"name":"Legacy"},{"name":"Orders"},{"name":"Pricing"},{"name":"Products"},{"name":"Quotes"},{"name":"Store"},{"name":"Stores"},{"name":"Uploads"},{"name":"Webhooks"},{"name":"Webhook subscriptions"}],"components":{"securitySchemes":{"default":{"type":"http","scheme":"bearer","description":"You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>."}},"schemas":{"AnalyticsSummaryParameter":{"type":"string","description":"Closed set of supported complete-day reporting periods.","enum":["7d","30d","90d","365d"]},"NullableString":{"type":["string","null"],"description":"Untranslatable braille modal text. Null restores the built-in default copy.","example":"Some of your text cannot be turned into braille automatically. Send us your design and we will quote it for you."},"AnalyticsSummaryLinks":{"type":"object","properties":{"self":{"type":"string"},"documentation":{"type":"string"}}},"AnalyticsSummaryMeta":{"type":"object","properties":{"api_version":{"type":"string"},"request_id":{"type":"string"}}},"MissingApiKeyProblem":{"type":"object","example":{"type":"https://www.signcustomiser.com/help/api/problems/missing_api_key","title":"Missing API key","status":401,"code":"missing_api_key","detail":"Provide a store API key as a bearer token: Authorization: Bearer <api key>. Keys are created in the merchant admin under Integrate → API Tokens.","request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"},"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"InsufficientScopeProblem":{"type":"object","example":{"type":"https://www.signcustomiser.com/help/api/problems/insufficient_scope","title":"Insufficient scope","status":403,"code":"insufficient_scope","detail":"This operation requires the analytics:read scope.","required_scopes":["analytics:read"],"granted_scopes":[],"request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"},"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"required_scopes":{"$ref":"#/components/schemas/OrderProducts"},"granted_scopes":{"type":"array"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"OrderProducts":{"type":"array","description":"The line items in the order.","example":[{"external_id":"product-987654321","quantity":2,"price":2999}],"items":{"type":"string"}},"AnalyticsSummaryResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/AnalyticsSummaryErrors"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"AnalyticsSummaryErrors":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSummaryError"}},"AnalyticsSummaryError":{"type":"object","properties":{"parameter":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"},"allowed_values":{"$ref":"#/components/schemas/OrderProducts"}}},"RateLimitedProblem":{"type":"object","example":{"type":"https://www.signcustomiser.com/help/api/problems/rate_limited","title":"Too many requests","status":429,"code":"rate_limited","detail":"You have exceeded the request limit for this API key.","retry_after":60,"request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"},"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"retry_after":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserBackboardItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"backboard_type":{"type":"string"},"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"outline_size":{"$ref":"#/components/schemas/NullableString"},"allow_multilayer":{"type":"boolean"},"custom_background_enabled":{"type":"boolean"},"show_custom_background":{"type":"boolean"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"geometry":{"$ref":"#/components/schemas/CustomiserBackboardItemGeometry"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserPricingPriceListRowPricePerLetterByPosition":{"type":"array","description":"Fixed-letter Advanced position schedule in integer minor currency units; null clears it.","example":[0,100,125],"items":{"type":"integer"}},"CustomiserBackboardItemGeometry":{"type":"object","properties":{"has_custom_shape":{"type":"boolean"},"status":{"$ref":"#/components/schemas/NullableString"},"failure_reason":{"$ref":"#/components/schemas/NullableString"},"measured_at":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserPrototypePagination":{"type":"object","properties":{"has_more":{"type":"boolean"},"next_cursor":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserPrototypeLinks":{"type":"object","properties":{"self":{"type":"string"},"documentation":{"type":"string"},"next":{"$ref":"#/components/schemas/NullableString"}}},"InsufficientScopeProblem2":{"type":"object","example":{"type":"https://www.signcustomiser.com/help/api/problems/insufficient_scope","title":"Insufficient scope","status":403,"code":"insufficient_scope","detail":"This operation requires the customisers:write scope, but the API key grants the following scopes: customisers:read. Create a key that includes the required scope in the merchant admin under Integrate → API Tokens.","required_scopes":["customisers:write"],"granted_scopes":["customisers:read"],"request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"},"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"required_scopes":{"$ref":"#/components/schemas/OrderProducts"},"granted_scopes":{"$ref":"#/components/schemas/OrderProducts"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserBackboardData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"backboard_type":{"type":"string"},"letter_type_ids":{"type":"array"},"outline_size":{"$ref":"#/components/schemas/NullableString"},"allow_multilayer":{"type":"boolean"},"custom_background_enabled":{"type":"boolean"},"show_custom_background":{"type":"boolean"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"geometry":{"$ref":"#/components/schemas/CustomiserBackboardItemGeometry"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserValidateCreateResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserOneOfErrors":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserOneOfError"}},"CustomiserOneOfError":{"type":"object","properties":{"pointer":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"}}},"CustomiserBackboardPriceMultiplierTarget":{"type":"string","description":"What price_multiplier multiplies. Defaults to base_letter_price.","example":"base_letter_price","enum":["base_letter_price","total_price"]},"BackboardValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BackboardValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"BackboardValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backboard_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"BackboardValidateCreateRequest":{"type":"object","properties":{"name":{"type":"string","description":"The backboard's display name — the same body the create accepts.","example":"Clear Acrylic Panel"}},"required":["name"]},"BackboardReorderResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"missing_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"unknown_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"duplicate_ids":{"type":"array"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"BackboardReorderRequest":{"type":"object","properties":{"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["backboard_ids"]},"CustomiserBackboardResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"referenced_by":{"$ref":"#/components/schemas/CustomiserBackboardReferencedBy"},"referenced_by_count":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserBackboardReferencedBy":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserBackboardReferencedBy2"}},"CustomiserBackboardReferencedBy2":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"}}},"ShapeValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ShapeValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ShapeValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backboard_id":{"type":"integer"},"valid":{"type":"boolean"}}},"BackboardValidateUpdateRequest":{"type":"object","properties":{"name":{"type":"string","description":"The backboard's display name — the same body the update accepts.","example":"Clear Acrylic Panel"}}},"BackboardImageResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/BackboardImageErrors"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"BackboardImageErrors":{"type":"array","items":{"$ref":"#/components/schemas/BackboardImageError"}},"BackboardImageError":{"type":"object","properties":{"pointer":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"},"allowed_values":{"$ref":"#/components/schemas/OrderProducts"}}},"BackboardImageRequest":{"type":"object","properties":{"upload_id":{"type":"string","description":"The id of an upload created with this slot's purpose whose bytes have already been sent to its presigned target.","example":"upl_9f2c4b7ad13e6058ba41cd"}},"required":["upload_id"]},"BackboardImageObject":{"type":"string","description":"The receipt type discriminator (closed set), shared by every file slot on this API.","enum":["file_deletion_receipt"]},"BackboardImageSlot":{"type":"string","description":"The image slot that was cleared.","enum":["image","button_image"]},"BackboardImageRemovedFile":{"type":"object","properties":{"filename":{"type":"string"},"url":{"type":"string"}}},"ImageValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backboard_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserBacklightResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserBacklightData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserBacklightData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"CustomiserBacklightItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"colour_type":{"type":"string"},"hexcode":{"type":"string"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/NullableString"},"texture":{"type":"string"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"texture_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserBacklightResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserBacklightItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserBacklightRequest":{"type":"object","properties":{"colour_type":{"$ref":"#/components/schemas/CustomiserBacklightColourType"},"texture":{"$ref":"#/components/schemas/CustomiserBacklightTexture"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The backlight's display name.","example":"Warm White Glow"},"description":{"type":"string","description":"A description shown alongside the option.","example":"A soft warm glow"},"hexcode":{"type":"string","description":"The colour the backlight glows, as a hex value. Required in practice for a single backlight.","example":"#FFEEDD"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/OrderProducts"},"min_width_cm":{"type":"number","description":"Minimum sign width, in centimetres, before the backlight is offered.","example":null},"min_height_cm":{"type":"number","description":"Minimum sign height, in centimetres, before the backlight is offered.","example":null},"base_price":{"type":"integer","description":"A flat amount added when the backlight is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the backlight is chosen.","example":1.2}},"required":["name"]},"CustomiserBacklightColourType":{"type":"string","description":"How the backlight renders. Defaults to single.","example":"single","enum":["single","multi","flow"]},"CustomiserBacklightTexture":{"type":"string","description":"The backlight's surface finish. Defaults to flat.","example":"flat","enum":["flat","metallic","custom_texture","carved"]},"CustomiserBacklightMinSizeRule":{"type":"string","description":"How the minimum width and height combine. Defaults to any.","example":"any","enum":["any","all"]},"BacklightValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BacklightValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"BacklightValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backlight_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"BacklightValidateCreateRequest":{"type":"object","properties":{"name":{"type":"string","description":"The backlight's display name — the same body the create accepts.","example":"Warm White Glow"},"hexcode":{"type":"string","description":"The colour the backlight glows, as a hex value.","example":"#FFEEDD"}},"required":["name"]},"BacklightReorderRequest":{"type":"object","properties":{"backlight_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["backlight_ids"]},"CustomiserBacklightRequest2":{"type":"object","properties":{"colour_type":{"$ref":"#/components/schemas/CustomiserBacklightColourType"},"texture":{"$ref":"#/components/schemas/CustomiserBacklightTexture"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The backlight's display name.","example":"Warm White Glow"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"A soft warm glow"},"hexcode":{"type":"string","description":"The colour the backlight glows, as a hex value; null clears it.","example":"#FFEEDD"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/OrderProducts"},"min_width_cm":{"type":"number","description":"Minimum sign width, in centimetres, before the backlight is offered; null clears it.","example":null},"min_height_cm":{"type":"number","description":"Minimum sign height, in centimetres, before the backlight is offered; null clears it.","example":null},"base_price":{"type":"integer","description":"A flat amount added when the backlight is chosen, in integer minor units.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the backlight is chosen.","example":1.2}}},"BacklightValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BacklightValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"BacklightValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backlight_id":{"type":"integer"},"valid":{"type":"boolean"}}},"BacklightImageResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BacklightImageData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"BacklightImageData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"colour_type":{"type":"string"},"hexcode":{"type":"string"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/NullableString"},"texture":{"type":"string"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"type":"string"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"texture_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"BacklightImageSlot":{"type":"string","description":"The image slot that was cleared.","enum":["image","example_image","texture_image"]},"ImageValidateDeleteResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData2"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData2":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"backlight_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserColourMultiColourHexcodes":{"type":["array","null"],"description":"The hex values a multi or flow colour cycles through.","example":["#FF0000","#00FF00"],"items":{"type":"string"}},"NullableNumber":{"type":["number","null"],"description":"Minimum sign width, in centimetres, before the colour is offered.","example":null},"ColourValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ColourValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ColourValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"colour_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"ColourReorderRequest":{"type":"object","properties":{"colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["colour_ids"]},"ColourValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ColourValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ColourValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"colour_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse3":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData3"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData3":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"colour_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserExtraItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"input_type":{"type":"string"},"true_label":{"type":"string"},"false_label":{"type":"string"},"default_value":{"type":"boolean"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"type":"array"},"no_colour_exclusions":{"type":"array"},"select_options":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserExtraData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"input_type":{"type":"string"},"true_label":{"$ref":"#/components/schemas/NullableString"},"false_label":{"$ref":"#/components/schemas/NullableString"},"default_value":{"type":"boolean"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"type":"array"},"no_colour_exclusions":{"type":"array"},"select_options":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserExtraNoColourExclusions":{"type":"array","description":"Every priced row of the tier, replacing the current set; an empty list clears them.","example":[{"size_id":3,"base_price":{"line_1":2600,"line_2":3000,"line_3":3500},"letter_price":{"line_1":400,"line_2":450,"line_3":500}}],"items":{"type":"object"}},"CustomiserExtraYesColourExclusionLetterPart":{"type":"string","description":"The letter part the exclusion is scoped to, or null for the whole letter.","example":"face","enum":["face","back","side","top","bottom","left","right","halo","trim","tube"]},"ExtraValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ExtraValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ExtraValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"extra_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"ExtraReorderRequest":{"type":"object","properties":{"extra_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["extra_ids"]},"ExtraValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ExtraValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ExtraValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"extra_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ExtraImageSlot":{"type":"string","description":"The image slot that was cleared.","enum":["image"]},"ImageValidateDeleteResponse4":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData4"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData4":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"extra_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse5":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData5"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData5":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"extra_id":{"type":"integer"},"choice_value":{"type":"string"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserFontItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"line_height":{"type":"number"},"uses_default_line_height":{"type":"boolean"},"stencil_safe":{"type":"boolean"},"min_height_smallest_letter_cm":{"type":"number"},"min_height_uppercase_letter_cm":{"$ref":"#/components/schemas/NullableString"},"font_tier_id":{"type":"integer"},"length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"minimum_size_id":{"$ref":"#/components/schemas/NullableString"},"minimum_legacy_fixed_height_size_id":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"font_file":{"$ref":"#/components/schemas/CustomiserFontItemFontFile"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFontItemFontFile":{"type":"object","properties":{"has_font_file":{"type":"boolean"},"family_name":{"type":"string"},"ttf_url":{"type":"string"}}},"CustomiserFontData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"line_height":{"$ref":"#/components/schemas/NullableString"},"uses_default_line_height":{"type":"boolean"},"stencil_safe":{"type":"boolean"},"min_height_smallest_letter_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_uppercase_letter_cm":{"$ref":"#/components/schemas/NullableString"},"font_tier_id":{"type":"integer"},"length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"minimum_size_id":{"$ref":"#/components/schemas/NullableString"},"minimum_legacy_fixed_height_size_id":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"font_file":{"$ref":"#/components/schemas/CustomiserFontFontFile"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFontFontFile":{"type":"object","properties":{"has_font_file":{"type":"boolean"},"family_name":{"$ref":"#/components/schemas/NullableString"},"ttf_url":{"$ref":"#/components/schemas/NullableString"}}},"FontValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FontValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FontValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"font_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"FontReorderItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"line_height":{"$ref":"#/components/schemas/NullableString"},"uses_default_line_height":{"type":"boolean"},"stencil_safe":{"type":"boolean"},"min_height_smallest_letter_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_uppercase_letter_cm":{"$ref":"#/components/schemas/NullableString"},"font_tier_id":{"type":"integer"},"length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"minimum_size_id":{"$ref":"#/components/schemas/NullableString"},"minimum_legacy_fixed_height_size_id":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"font_file":{"$ref":"#/components/schemas/CustomiserFontItemFontFile"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"FontReorderRequest":{"type":"object","properties":{"font_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["font_ids"]},"CustomiserFontResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFontItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FileValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FileValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FileValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"font_id":{"type":"integer"},"valid":{"type":"boolean"}}},"CustomiserFontTierItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"currency":{"type":"string"},"rows":{"$ref":"#/components/schemas/CustomiserFontTierItemRows"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFontTierItemRows":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFontTierItemRow"}},"CustomiserFontTierItemRow":{"type":"object","properties":{"size_id":{"type":"integer"},"base_price":{"$ref":"#/components/schemas/CustomiserFontTierItemRowBasePrice"},"letter_price":{"$ref":"#/components/schemas/CustomiserFontTierItemRowBasePrice"},"letter_price_by_position":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserFontTierItemRowBasePrice":{"type":"object","properties":{"line_1":{"type":"integer"},"line_2":{"type":"integer"},"line_3":{"type":"integer"},"line_4":{"$ref":"#/components/schemas/NullableString"},"line_5":{"$ref":"#/components/schemas/NullableString"},"line_6":{"$ref":"#/components/schemas/NullableString"},"line_7":{"$ref":"#/components/schemas/NullableString"},"line_8":{"$ref":"#/components/schemas/NullableString"},"line_9":{"$ref":"#/components/schemas/NullableString"},"line_10":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserFontTierResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFontTierData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserFontTierData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"currency":{"type":"string"},"rows":{"$ref":"#/components/schemas/CustomiserFontTierRows"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFontTierRows":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFontTierRow"}},"CustomiserFontTierRow":{"type":"object","properties":{"size_id":{"type":"integer"},"base_price":{"$ref":"#/components/schemas/CustomiserFontTierRowBasePrice"},"letter_price":{"$ref":"#/components/schemas/CustomiserFontTierRowBasePrice"},"letter_price_by_position":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserFontTierRowBasePrice":{"type":"object","properties":{"line_1":{"type":"integer"},"line_2":{"$ref":"#/components/schemas/NullableString"},"line_3":{"$ref":"#/components/schemas/NullableString"},"line_4":{"$ref":"#/components/schemas/NullableString"},"line_5":{"$ref":"#/components/schemas/NullableString"},"line_6":{"$ref":"#/components/schemas/NullableString"},"line_7":{"$ref":"#/components/schemas/NullableString"},"line_8":{"$ref":"#/components/schemas/NullableString"},"line_9":{"$ref":"#/components/schemas/NullableString"},"line_10":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserFontTierLetterPricingMode":{"type":"string","description":"How the tier charges for letters. Defaults to every_letter_cost.","example":"every_letter_cost","enum":["every_letter_cost","price_by_position"]},"CustomiserProductCart":{"type":"object","description":"The shipping members to change; the rest are left alone.","example":{"calculation":"volumetric","volumetric_divisor":5000},"properties":{}},"FontTierValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FontTierValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FontTierValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"font_tier_id":{"type":"integer"},"valid":{"type":"boolean"}}},"CustomiserFormItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"form_type":{"type":"string"},"button_label":{"type":"string"},"subject":{"type":"string"},"recipient_emails":{"$ref":"#/components/schemas/OrderProducts"},"save_customer_details":{"type":"boolean"},"post_submission_action":{"type":"string"},"redirect_url":{"$ref":"#/components/schemas/NullableString"},"emails_enabled":{"type":"boolean"},"suppressed_emails":{"type":"array"},"fields":{"$ref":"#/components/schemas/CustomiserFormItemFields"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFormItemFields":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFormItemField"}},"CustomiserFormItemField":{"type":"object","properties":{"field_id":{"type":"string"},"name":{"type":"string"},"input_type":{"type":"string"},"required":{"type":"boolean"},"customer_field":{"type":"string"},"info":{"$ref":"#/components/schemas/NullableString"},"placeholder":{"$ref":"#/components/schemas/NullableString"},"checkbox_value":{"$ref":"#/components/schemas/NullableString"},"min":{"$ref":"#/components/schemas/NullableString"},"max":{"$ref":"#/components/schemas/NullableString"},"min_date":{"$ref":"#/components/schemas/NullableString"},"max_date":{"$ref":"#/components/schemas/NullableString"},"allow_multiple":{"type":"boolean"},"reply_to":{"type":"boolean"},"options":{"type":"array"}}},"CustomiserFormData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"form_type":{"type":"string"},"button_label":{"$ref":"#/components/schemas/NullableString"},"subject":{"$ref":"#/components/schemas/NullableString"},"recipient_emails":{"type":"array"},"save_customer_details":{"type":"boolean"},"post_submission_action":{"type":"string"},"redirect_url":{"$ref":"#/components/schemas/NullableString"},"emails_enabled":{"type":"boolean"},"suppressed_emails":{"type":"array"},"fields":{"type":"array"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserFormFormType":{"type":"string","description":"What the form collects.","example":"quote","enum":["quote","custom_design"]},"CustomiserFormFieldInputType":{"type":"string","description":"The input the field renders as.","example":"email","enum":["text","textarea","email","tel","number","date","checkbox","select","multi_choice","file"]},"FormValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FormValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FormValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"form_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"FormReorderRequest":{"type":"object","properties":{"form_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["form_ids"]},"CustomiserFormResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFormItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserOneOf":{"description":"Stored type outside the catalogue","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"request_id":{"type":"string"}}},"CustomiserFormResponse2":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"referenced_by":{"$ref":"#/components/schemas/CustomiserFormReferencedBy"},"referenced_by_count":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserFormReferencedBy":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFormReferencedBy2"}},"CustomiserFormReferencedBy2":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"integer"}}},"CustomiserFormResponse3":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf2"}],"required":["type","title","status","code","detail","request_id"]},"CustomiserOneOf2":{"description":"Unknown parameter","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"request_id":{"type":"string"}}},"FormValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FormValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"FormValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"form_id":{"type":"integer"},"valid":{"type":"boolean"}}},"FormValidateUpdateResponse":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"}],"required":["type","title","status","code","detail","request_id"]},"CustomiserIconResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserIconData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserIconData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserIconItem"}},"CustomiserIconItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"svg":{"type":"string"},"rendered_svg":{"type":"string"},"min_height_cm":{"type":"integer"},"icon_font":{"$ref":"#/components/schemas/CustomiserIconItemIconFont"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserIconItemIconFont":{"type":"object","properties":{"glyph_code":{"type":"string"},"font_family":{"type":"string"},"ttf_url":{"type":"string"}}},"CustomiserIconResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserIconItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"IconValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/IconValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"IconValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"icon_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"IconReorderRequest":{"type":"object","properties":{"icon_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["icon_ids"]},"IconValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/IconValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"IconValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"icon_id":{"type":"integer"},"valid":{"type":"boolean"}}},"CustomiserJacketResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserJacketData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserJacketData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserJacketItem"}},"CustomiserJacketItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"jacket_type":{"type":"string"},"excluded_colour_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserJacketResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserJacketItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"JacketValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/JacketValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"JacketValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"jacket_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"JacketReorderRequest":{"type":"object","properties":{"jacket_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["jacket_ids"]},"JacketValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/JacketValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"JacketValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"jacket_id":{"type":"integer"},"valid":{"type":"boolean"}}},"NullableBoolean":{"type":["boolean","null"],"example":false},"CustomiserLabelCustomDesignSteps":{"type":["array","null"],"description":"Custom Design Steps. Read-only: see the operation description.","items":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignStep"}},"CustomiserLabelCustomDesignStep":{"type":"object","properties":{"header":{"type":"string"},"description":{"type":"string"}}},"CustomiserLabelCustomDesignType":{"type":["string","null"],"description":"Custom Design Type. Null while the label has never been worded and the customiser has no default language.","enum":["none","link","form","live",null]},"CustomiserLabelAdditionalInformation":{"type":"object","properties":{"header":{"type":"string"},"description":{"type":"string"},"image_url":{"type":"string"}}},"CustomiserLabelBackgroundImage":{"type":"object","properties":{"name":{"type":"string"},"image_url":{"type":"string"},"width_cm":{"type":"number"},"height_cm":{"type":"number"}}},"CustomiserLabelResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLabelData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserLabelData":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"adding_to_cart":{"$ref":"#/components/schemas/NullableString"},"allow_custom_background":{"$ref":"#/components/schemas/NullableBoolean"},"apply_style":{"$ref":"#/components/schemas/NullableString"},"aspect_ratio_warning":{"$ref":"#/components/schemas/NullableString"},"backboard_upload_error":{"$ref":"#/components/schemas/NullableString"},"backgrounds":{"$ref":"#/components/schemas/NullableString"},"backlight_title":{"$ref":"#/components/schemas/NullableString"},"border_included":{"$ref":"#/components/schemas/NullableString"},"bottom_side":{"$ref":"#/components/schemas/NullableString"},"braille_fixed_size_info_label":{"$ref":"#/components/schemas/NullableString"},"braille_frame_too_small_message":{"$ref":"#/components/schemas/NullableString"},"braille_overview_fitted_to_screen_label":{"$ref":"#/components/schemas/NullableString"},"braille_translation_failed_message":{"$ref":"#/components/schemas/NullableString"},"braille_translation_pending_message":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_label":{"$ref":"#/components/schemas/NullableString"},"cancel":{"$ref":"#/components/schemas/NullableString"},"char_limit_reached":{"$ref":"#/components/schemas/NullableString"},"character_limit_message":{"$ref":"#/components/schemas/NullableString"},"character_text":{"$ref":"#/components/schemas/NullableString"},"click_word_desktop":{"$ref":"#/components/schemas/NullableString"},"cm_button":{"$ref":"#/components/schemas/NullableString"},"colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"colour_title":{"$ref":"#/components/schemas/NullableString"},"contact_description":{"$ref":"#/components/schemas/NullableString"},"contact_header":{"$ref":"#/components/schemas/NullableString"},"copy_button":{"$ref":"#/components/schemas/NullableString"},"create_product_failed":{"$ref":"#/components/schemas/NullableString"},"creating_product":{"$ref":"#/components/schemas/NullableString"},"crop_confirm":{"$ref":"#/components/schemas/NullableString"},"crop_modal_title":{"$ref":"#/components/schemas/NullableString"},"crop_upload":{"$ref":"#/components/schemas/NullableString"},"custom_design_button_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_file_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_input_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_success":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_submit_message":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_total_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_link":{"$ref":"#/components/schemas/NullableString"},"custom_design_steps":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignSteps"},"custom_design_steps_header":{"$ref":"#/components/schemas/NullableString"},"custom_design_tab_title":{"$ref":"#/components/schemas/NullableString"},"custom_design_type":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignType"},"custom_form_header":{"$ref":"#/components/schemas/NullableString"},"custom_sizing_message":{"$ref":"#/components/schemas/NullableString"},"default_text":{"$ref":"#/components/schemas/NullableString"},"display_initial_background_image":{"$ref":"#/components/schemas/NullableBoolean"},"display_initial_review_image":{"$ref":"#/components/schemas/NullableBoolean"},"edit_design":{"$ref":"#/components/schemas/NullableString"},"editing_word":{"$ref":"#/components/schemas/NullableString"},"etsy_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copied_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copy_details_button":{"$ref":"#/components/schemas/NullableString"},"etsy_customisation_summary_label":{"$ref":"#/components/schemas/NullableString"},"etsy_download_preview_button":{"$ref":"#/components/schemas/NullableString"},"etsy_listing_url_missing":{"$ref":"#/components/schemas/NullableString"},"etsy_preparing_image":{"$ref":"#/components/schemas/NullableString"},"etsy_preview_error":{"$ref":"#/components/schemas/NullableString"},"etsy_return_button":{"$ref":"#/components/schemas/NullableString"},"etsy_step1":{"$ref":"#/components/schemas/NullableString"},"etsy_step2":{"$ref":"#/components/schemas/NullableString"},"etsy_step3":{"$ref":"#/components/schemas/NullableString"},"etsy_suggested_price_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_description":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_face_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_halo_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_icon_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_line_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_no":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_not_selected":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_side_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_title":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_trim_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_tube_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_uploaded_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_yes":{"$ref":"#/components/schemas/NullableString"},"example":{"$ref":"#/components/schemas/NullableString"},"extra_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"extras_title":{"$ref":"#/components/schemas/NullableString"},"file_label":{"$ref":"#/components/schemas/NullableString"},"file_upload_label":{"$ref":"#/components/schemas/NullableString"},"font_title":{"$ref":"#/components/schemas/NullableString"},"generating_image":{"$ref":"#/components/schemas/NullableString"},"header_text":{"$ref":"#/components/schemas/NullableString"},"height_input":{"$ref":"#/components/schemas/NullableString"},"highlights":{"$ref":"#/components/schemas/CustomiserColourMultiColourHexcodes"},"icon_button_label":{"$ref":"#/components/schemas/NullableString"},"icon_title":{"$ref":"#/components/schemas/NullableString"},"inch_button":{"$ref":"#/components/schemas/NullableString"},"inclusions":{"$ref":"#/components/schemas/NullableString"},"inclusions_header":{"$ref":"#/components/schemas/NullableString"},"invalid_characters":{"$ref":"#/components/schemas/NullableString"},"jacket_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"jacket_title":{"$ref":"#/components/schemas/NullableString"},"label_under_price":{"$ref":"#/components/schemas/NullableString"},"left_side":{"$ref":"#/components/schemas/NullableString"},"letter_parts_description":{"$ref":"#/components/schemas/NullableString"},"letter_parts_title":{"$ref":"#/components/schemas/NullableString"},"lightbox_product_type_title":{"$ref":"#/components/schemas/NullableString"},"line_limit_reached":{"$ref":"#/components/schemas/NullableString"},"lines_text":{"$ref":"#/components/schemas/NullableString"},"loading_manual_clear_text":{"$ref":"#/components/schemas/NullableString"},"logo_button_text":{"$ref":"#/components/schemas/NullableString"},"logo_text":{"$ref":"#/components/schemas/NullableString"},"logo_upload_design_ready":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_file_too_large":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_email_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_name_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submit_button":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_try_another":{"$ref":"#/components/schemas/NullableString"},"main_tab_title":{"$ref":"#/components/schemas/NullableString"},"manual_button":{"$ref":"#/components/schemas/NullableString"},"material_mounting_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_title":{"$ref":"#/components/schemas/NullableString"},"max_sign_char":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_link":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_label":{"$ref":"#/components/schemas/NullableString"},"min_character_message":{"$ref":"#/components/schemas/NullableString"},"min_height_sign_warning":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_title":{"$ref":"#/components/schemas/NullableString"},"mounting_letter_part_title":{"$ref":"#/components/schemas/NullableString"},"mounting_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_title":{"$ref":"#/components/schemas/NullableString"},"options_tab":{"$ref":"#/components/schemas/NullableString"},"preview_note":{"$ref":"#/components/schemas/NullableString"},"price":{"$ref":"#/components/schemas/NullableString"},"price_after_label":{"$ref":"#/components/schemas/NullableString"},"price_box_text":{"$ref":"#/components/schemas/NullableString"},"price_button_add":{"$ref":"#/components/schemas/NullableString"},"price_button_finish":{"$ref":"#/components/schemas/NullableString"},"product_title":{"$ref":"#/components/schemas/NullableString"},"product_type_title":{"$ref":"#/components/schemas/NullableString"},"qualities":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignSteps"},"qualities_header":{"$ref":"#/components/schemas/NullableString"},"quote_button":{"$ref":"#/components/schemas/NullableString"},"recenter":{"$ref":"#/components/schemas/NullableString"},"recenter_warning":{"$ref":"#/components/schemas/NullableString"},"retry":{"$ref":"#/components/schemas/NullableString"},"right_side":{"$ref":"#/components/schemas/NullableString"},"rotation":{"$ref":"#/components/schemas/NullableString"},"shape_finished_size":{"$ref":"#/components/schemas/NullableString"},"shape_proportions_fixed_by":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_fixed_proportions":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_empty_feasible_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_canonical_bounds":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_intent":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_preset_requires_custom":{"$ref":"#/components/schemas/NullableString"},"share_design":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_description":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_title":{"$ref":"#/components/schemas/NullableString"},"show_background_preview":{"$ref":"#/components/schemas/NullableBoolean"},"show_contact_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_custom_design":{"$ref":"#/components/schemas/NullableBoolean"},"show_custom_design_link":{"$ref":"#/components/schemas/NullableBoolean"},"show_inclusion_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_preview_note":{"$ref":"#/components/schemas/NullableBoolean"},"show_review_images":{"$ref":"#/components/schemas/NullableBoolean"},"show_review_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_styled_note":{"$ref":"#/components/schemas/NullableBoolean"},"show_text_editor_overlay":{"$ref":"#/components/schemas/NullableBoolean"},"sign_header":{"$ref":"#/components/schemas/NullableString"},"size_title":{"$ref":"#/components/schemas/NullableString"},"slider_button":{"$ref":"#/components/schemas/NullableString"},"summary_header":{"$ref":"#/components/schemas/NullableString"},"support_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"support_colour_title":{"$ref":"#/components/schemas/NullableString"},"support_title":{"$ref":"#/components/schemas/NullableString"},"tap_word_mobile":{"$ref":"#/components/schemas/NullableString"},"text_align_center":{"$ref":"#/components/schemas/NullableString"},"text_align_left":{"$ref":"#/components/schemas/NullableString"},"text_align_right":{"$ref":"#/components/schemas/NullableString"},"text_align_title":{"$ref":"#/components/schemas/NullableString"},"text_button_label":{"$ref":"#/components/schemas/NullableString"},"text_label":{"$ref":"#/components/schemas/NullableString"},"text_orientation_horizontal":{"$ref":"#/components/schemas/NullableString"},"text_orientation_title":{"$ref":"#/components/schemas/NullableString"},"text_orientation_vertical_stacked":{"$ref":"#/components/schemas/NullableString"},"text_title":{"$ref":"#/components/schemas/NullableString"},"top_side":{"$ref":"#/components/schemas/NullableString"},"upload_background":{"$ref":"#/components/schemas/NullableString"},"uploading_images":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_cancel":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_continue":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_description":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_error":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_title":{"$ref":"#/components/schemas/NullableString"},"visualiser_off":{"$ref":"#/components/schemas/NullableString"},"visualiser_on":{"$ref":"#/components/schemas/NullableString"},"width_input":{"$ref":"#/components/schemas/NullableString"},"zoom_in":{"$ref":"#/components/schemas/NullableString"},"zoom_out":{"$ref":"#/components/schemas/NullableString"},"additional_information":{"$ref":"#/components/schemas/CustomiserLabelAdditionalInformation2"},"background_images":{"$ref":"#/components/schemas/CustomiserLabelBackgroundImages"},"product_image_url":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserLabelAdditionalInformation2":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLabelAdditionalInformation"}},"CustomiserLabelBackgroundImages":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLabelBackgroundImage"}},"CustomiserLabelResponse2":{"oneOf":[{"$ref":"#/components/schemas/CustomiserLabelOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"}],"required":["type","title","status","code","detail","request_id"]},"CustomiserLabelOneOf":{"description":"Validation failed","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/BackboardImageErrors"},"request_id":{"type":"string"}}},"CustomiserLabelRequest":{"type":"object","properties":{"adding_to_cart":{"$ref":"#/components/schemas/NullableString"},"allow_custom_background":{"type":"boolean","description":"Allow Custom Background. Turns the section on or off; takes a boolean, never null.","example":true},"apply_style":{"$ref":"#/components/schemas/NullableString"},"aspect_ratio_warning":{"$ref":"#/components/schemas/NullableString"},"backboard_upload_error":{"$ref":"#/components/schemas/NullableString"},"backgrounds":{"$ref":"#/components/schemas/NullableString"},"backlight_title":{"$ref":"#/components/schemas/NullableString"},"border_included":{"$ref":"#/components/schemas/NullableString"},"bottom_side":{"$ref":"#/components/schemas/NullableString"},"braille_fixed_size_info_label":{"$ref":"#/components/schemas/NullableString"},"braille_frame_too_small_message":{"$ref":"#/components/schemas/NullableString"},"braille_overview_fitted_to_screen_label":{"$ref":"#/components/schemas/NullableString"},"braille_translation_failed_message":{"$ref":"#/components/schemas/NullableString"},"braille_translation_pending_message":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_label":{"$ref":"#/components/schemas/NullableString"},"cancel":{"$ref":"#/components/schemas/NullableString"},"char_limit_reached":{"$ref":"#/components/schemas/NullableString"},"character_limit_message":{"$ref":"#/components/schemas/NullableString"},"character_text":{"$ref":"#/components/schemas/NullableString"},"click_word_desktop":{"$ref":"#/components/schemas/NullableString"},"cm_button":{"$ref":"#/components/schemas/NullableString"},"colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"colour_title":{"$ref":"#/components/schemas/NullableString"},"contact_description":{"$ref":"#/components/schemas/NullableString"},"contact_header":{"$ref":"#/components/schemas/NullableString"},"copy_button":{"$ref":"#/components/schemas/NullableString"},"create_product_failed":{"$ref":"#/components/schemas/NullableString"},"creating_product":{"$ref":"#/components/schemas/NullableString"},"crop_confirm":{"$ref":"#/components/schemas/NullableString"},"crop_modal_title":{"$ref":"#/components/schemas/NullableString"},"crop_upload":{"$ref":"#/components/schemas/NullableString"},"custom_design_button_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_file_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_input_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_success":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_submit_message":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_total_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_link":{"$ref":"#/components/schemas/NullableString"},"custom_design_steps_header":{"$ref":"#/components/schemas/NullableString"},"custom_design_tab_title":{"$ref":"#/components/schemas/NullableString"},"custom_design_type":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignType2"},"custom_form_header":{"$ref":"#/components/schemas/NullableString"},"custom_sizing_message":{"$ref":"#/components/schemas/NullableString"},"default_text":{"$ref":"#/components/schemas/NullableString"},"display_initial_background_image":{"type":"boolean","description":"Display Initial Background Image. Turns the section on or off; takes a boolean, never null.","example":true},"display_initial_review_image":{"type":"boolean","description":"Display Initial Review Image. Turns the section on or off; takes a boolean, never null.","example":true},"edit_design":{"$ref":"#/components/schemas/NullableString"},"editing_word":{"$ref":"#/components/schemas/NullableString"},"etsy_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copied_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copy_details_button":{"$ref":"#/components/schemas/NullableString"},"etsy_customisation_summary_label":{"$ref":"#/components/schemas/NullableString"},"etsy_download_preview_button":{"$ref":"#/components/schemas/NullableString"},"etsy_listing_url_missing":{"$ref":"#/components/schemas/NullableString"},"etsy_preparing_image":{"$ref":"#/components/schemas/NullableString"},"etsy_preview_error":{"$ref":"#/components/schemas/NullableString"},"etsy_return_button":{"$ref":"#/components/schemas/NullableString"},"etsy_step1":{"$ref":"#/components/schemas/NullableString"},"etsy_step2":{"$ref":"#/components/schemas/NullableString"},"etsy_step3":{"$ref":"#/components/schemas/NullableString"},"etsy_suggested_price_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_description":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_face_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_halo_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_icon_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_line_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_no":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_not_selected":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_side_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_title":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_trim_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_tube_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_uploaded_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_yes":{"$ref":"#/components/schemas/NullableString"},"example":{"$ref":"#/components/schemas/NullableString"},"extra_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"extras_title":{"$ref":"#/components/schemas/NullableString"},"file_label":{"$ref":"#/components/schemas/NullableString"},"file_upload_label":{"$ref":"#/components/schemas/NullableString"},"font_title":{"$ref":"#/components/schemas/NullableString"},"generating_image":{"$ref":"#/components/schemas/NullableString"},"header_text":{"$ref":"#/components/schemas/NullableString"},"height_input":{"$ref":"#/components/schemas/NullableString"},"icon_button_label":{"$ref":"#/components/schemas/NullableString"},"icon_title":{"$ref":"#/components/schemas/NullableString"},"inch_button":{"$ref":"#/components/schemas/NullableString"},"inclusions":{"$ref":"#/components/schemas/NullableString"},"inclusions_header":{"$ref":"#/components/schemas/NullableString"},"invalid_characters":{"$ref":"#/components/schemas/NullableString"},"jacket_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"jacket_title":{"$ref":"#/components/schemas/NullableString"},"label_under_price":{"$ref":"#/components/schemas/NullableString"},"left_side":{"$ref":"#/components/schemas/NullableString"},"letter_parts_description":{"$ref":"#/components/schemas/NullableString"},"letter_parts_title":{"$ref":"#/components/schemas/NullableString"},"lightbox_product_type_title":{"$ref":"#/components/schemas/NullableString"},"line_limit_reached":{"$ref":"#/components/schemas/NullableString"},"lines_text":{"$ref":"#/components/schemas/NullableString"},"loading_manual_clear_text":{"$ref":"#/components/schemas/NullableString"},"logo_button_text":{"$ref":"#/components/schemas/NullableString"},"logo_text":{"$ref":"#/components/schemas/NullableString"},"logo_upload_design_ready":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_file_too_large":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_email_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_name_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submit_button":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_try_another":{"$ref":"#/components/schemas/NullableString"},"main_tab_title":{"$ref":"#/components/schemas/NullableString"},"manual_button":{"$ref":"#/components/schemas/NullableString"},"material_mounting_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_title":{"$ref":"#/components/schemas/NullableString"},"max_sign_char":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_link":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_label":{"$ref":"#/components/schemas/NullableString"},"min_character_message":{"$ref":"#/components/schemas/NullableString"},"min_height_sign_warning":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_title":{"$ref":"#/components/schemas/NullableString"},"mounting_letter_part_title":{"$ref":"#/components/schemas/NullableString"},"mounting_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_title":{"$ref":"#/components/schemas/NullableString"},"options_tab":{"$ref":"#/components/schemas/NullableString"},"preview_note":{"$ref":"#/components/schemas/NullableString"},"price":{"$ref":"#/components/schemas/NullableString"},"price_after_label":{"$ref":"#/components/schemas/NullableString"},"price_box_text":{"$ref":"#/components/schemas/NullableString"},"price_button_add":{"$ref":"#/components/schemas/NullableString"},"price_button_finish":{"$ref":"#/components/schemas/NullableString"},"product_title":{"$ref":"#/components/schemas/NullableString"},"product_type_title":{"$ref":"#/components/schemas/NullableString"},"qualities_header":{"$ref":"#/components/schemas/NullableString"},"quote_button":{"$ref":"#/components/schemas/NullableString"},"recenter":{"$ref":"#/components/schemas/NullableString"},"recenter_warning":{"$ref":"#/components/schemas/NullableString"},"retry":{"$ref":"#/components/schemas/NullableString"},"right_side":{"$ref":"#/components/schemas/NullableString"},"rotation":{"$ref":"#/components/schemas/NullableString"},"shape_finished_size":{"$ref":"#/components/schemas/NullableString"},"shape_proportions_fixed_by":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_fixed_proportions":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_empty_feasible_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_canonical_bounds":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_intent":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_preset_requires_custom":{"$ref":"#/components/schemas/NullableString"},"share_design":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_description":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_title":{"$ref":"#/components/schemas/NullableString"},"show_background_preview":{"type":"boolean","description":"Show Background Preview. Turns the section on or off; takes a boolean, never null.","example":true},"show_contact_section":{"type":"boolean","description":"Show Contact Section. Turns the section on or off; takes a boolean, never null.","example":true},"show_custom_design":{"type":"boolean","description":"Show Custom Design. Turns the section on or off; takes a boolean, never null.","example":true},"show_custom_design_link":{"type":"boolean","description":"Show Custom Design Link. Turns the section on or off; takes a boolean, never null.","example":true},"show_inclusion_section":{"type":"boolean","description":"Show Inclusion Section. Turns the section on or off; takes a boolean, never null.","example":true},"show_preview_note":{"type":"boolean","description":"Show Preview Note. Turns the section on or off; takes a boolean, never null.","example":true},"show_review_images":{"type":"boolean","description":"Show Review Images. Turns the section on or off; takes a boolean, never null.","example":true},"show_review_section":{"type":"boolean","description":"Show Review Section. Turns the section on or off; takes a boolean, never null.","example":true},"show_styled_note":{"type":"boolean","description":"Show Styled Note. Turns the section on or off; takes a boolean, never null.","example":true},"show_text_editor_overlay":{"type":"boolean","description":"Show Text Editor Overlay. Turns the section on or off; takes a boolean, never null.","example":true},"sign_header":{"$ref":"#/components/schemas/NullableString"},"size_title":{"$ref":"#/components/schemas/NullableString"},"slider_button":{"$ref":"#/components/schemas/NullableString"},"summary_header":{"$ref":"#/components/schemas/NullableString"},"support_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"support_colour_title":{"$ref":"#/components/schemas/NullableString"},"support_title":{"$ref":"#/components/schemas/NullableString"},"tap_word_mobile":{"$ref":"#/components/schemas/NullableString"},"text_align_center":{"$ref":"#/components/schemas/NullableString"},"text_align_left":{"$ref":"#/components/schemas/NullableString"},"text_align_right":{"$ref":"#/components/schemas/NullableString"},"text_align_title":{"$ref":"#/components/schemas/NullableString"},"text_button_label":{"$ref":"#/components/schemas/NullableString"},"text_label":{"$ref":"#/components/schemas/NullableString"},"text_orientation_horizontal":{"$ref":"#/components/schemas/NullableString"},"text_orientation_title":{"$ref":"#/components/schemas/NullableString"},"text_orientation_vertical_stacked":{"$ref":"#/components/schemas/NullableString"},"text_title":{"$ref":"#/components/schemas/NullableString"},"top_side":{"$ref":"#/components/schemas/NullableString"},"upload_background":{"$ref":"#/components/schemas/NullableString"},"uploading_images":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_cancel":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_continue":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_description":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_error":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_title":{"$ref":"#/components/schemas/NullableString"},"visualiser_off":{"$ref":"#/components/schemas/NullableString"},"visualiser_on":{"$ref":"#/components/schemas/NullableString"},"width_input":{"$ref":"#/components/schemas/NullableString"},"zoom_in":{"$ref":"#/components/schemas/NullableString"},"zoom_out":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserLabelCustomDesignType2":{"type":"string","description":"Custom Design Type. One of: none, link, form, live.","example":"form","enum":["none","link","form","live"]},"LabelValidateUpdateOperation":{"type":"string","description":"The write this verdict is the dry run of (closed set).","enum":["update"]},"LabelProductImageResourceCustomDesignSteps":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignStep"}},"ProductImageValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProductImageValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ProductImageValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"valid":{"type":"boolean"}}},"CustomiserLanguageItem":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"source_language_code":{"type":"string"},"display_name":{"type":"string"},"status":{"type":"string"},"is_default":{"type":"boolean"},"translation_summary":{"$ref":"#/components/schemas/CustomiserLanguageItemTranslationSummary"},"ai_translation":{"$ref":"#/components/schemas/NullableString"},"published_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserLanguageItemTranslationSummary":{"type":"object","properties":{"key_count":{"type":"integer"},"required_key_count":{"type":"integer"},"translated_key_count":{"type":"integer"},"retired_key_count":{"type":"integer"},"missing_required_key_count":{"type":"integer"},"publishable":{"type":"boolean"}}},"CustomiserLanguageResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLanguageData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserLanguageData":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"source_language_code":{"type":"string"},"display_name":{"type":"string"},"status":{"type":"string"},"is_default":{"type":"boolean"},"translation_summary":{"$ref":"#/components/schemas/CustomiserLanguageItemTranslationSummary"},"ai_translation":{"$ref":"#/components/schemas/NullableString"},"published_at":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"LanguageCancelAiTranslationResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"reason":{"type":"string"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"LanguageValidateCancelAiTranslationResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LanguageValidateCancelAiTranslationData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LanguageValidateCancelAiTranslationData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"valid":{"type":"boolean"}}},"LanguageDefaultResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLanguageItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LanguagePublishResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"missing_keys":{"$ref":"#/components/schemas/OrderProducts"},"missing_key_count":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"LanguageTranslationResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"invalid_keys":{"$ref":"#/components/schemas/LanguageTranslationInvalidKeys"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"LanguageTranslationInvalidKeys":{"type":"array","items":{"$ref":"#/components/schemas/LanguageTranslationInvalidKey"}},"LanguageTranslationInvalidKey":{"type":"object","properties":{"key":{"type":"string"},"reason":{"type":"string"}}},"CustomiserLegacyFixedHeightSizItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"char_limit":{"type":"integer"},"line_limit":{"type":"integer"},"min_char":{"type":"integer"},"height_cm":{"type":"integer"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserLengthPricingItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"letter_pricing_method":{"type":"string"},"shipping":{"$ref":"#/components/schemas/CustomiserLengthPricingItemShipping"},"currency":{"type":"string"},"rows":{"$ref":"#/components/schemas/CustomiserLengthPricingItemRows"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserLengthPricingItemShipping":{"type":"object","properties":{"calculation":{"type":"string"},"volumetric_divisor":{"$ref":"#/components/schemas/NullableString"},"add_weight_to_product":{"type":"boolean"}}},"CustomiserLengthPricingItemRows":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLengthPricingItemRow"}},"CustomiserLengthPricingItemRow":{"type":"object","properties":{"base_price":{"type":"integer"},"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"length_limit_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_sq_cm":{"type":"number"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableString"},"width_modifier":{"$ref":"#/components/schemas/NullableString"},"height_modifier":{"$ref":"#/components/schemas/NullableString"},"length_modifier":{"$ref":"#/components/schemas/NullableString"},"max_price_multiplier":{"$ref":"#/components/schemas/NullableString"},"price_per_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_letter":{"type":"integer"},"price_per_letter_by_position":{"$ref":"#/components/schemas/NullableString"}}},"LengthPricingValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LengthPricingValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LengthPricingValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"length_pricing_id":{"type":"integer"},"valid":{"type":"boolean"}}},"LetterTypeLetterPartItem":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"name":{"$ref":"#/components/schemas/NullableString"},"description":{"$ref":"#/components/schemas/NullableString"},"optional":{"$ref":"#/components/schemas/NullableString"},"optional_label":{"$ref":"#/components/schemas/NullableString"},"optional_default_none":{"type":"boolean"},"lightsource":{"type":"boolean"},"custom_image":{"type":"boolean"},"show_default_upload_image":{"type":"boolean"},"show_in_text_editor":{"type":"boolean"},"text_enabled":{"type":"boolean"},"default_upload_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"LetterTypeLetterPartResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LetterTypeLetterPartItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LetterTypeLetterPartOneOf":{"description":"Letter part not declared","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"declared_letter_parts":{"$ref":"#/components/schemas/OrderProducts"},"request_id":{"type":"string"}}},"LetterPartValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LetterPartValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LetterPartValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"valid":{"type":"boolean"}}},"LetterTypeLetterPartResponse2":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"reason":{"type":"string"},"referenced_by":{"$ref":"#/components/schemas/CustomiserBackboardReferencedBy"},"referenced_by_count":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserActivateResponse":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"reason":{"type":"string"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"ImageValidateDeleteResponse6":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData6"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData6":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"ColourValidateCreateResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ColourValidateCreateData2"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ColourValidateCreateData2":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"colour_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"ColourValidateDeleteResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ColourValidateDeleteData2"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ColourValidateDeleteData2":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"colour_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse7":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData7"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData7":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"colour_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserLetterTypeResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLetterTypeData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserLetterTypeData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLetterTypeItem"}},"CustomiserLetterTypeItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"shape":{"$ref":"#/components/schemas/NullableString"},"letter_parts":{"$ref":"#/components/schemas/CustomiserLetterTypeItemLetterParts"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserLetterTypeItemLetterParts":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLetterTypeItemLetterPart"}},"CustomiserLetterTypeItemLetterPart":{"type":"object","properties":{"letter_part":{"type":"string"},"active":{"type":"boolean"}}},"CustomiserLetterTypeResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLetterTypeItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserLetterTypeShape":{"type":"string","description":"The body a lightbox letter type is built from. Required on a lightbox customiser and refused on every other category.","example":"rectangle","enum":["rectangle","cylinder"]},"CustomiserLetterTypeLetterParts":{"type":"array","description":"Which parts of the letter exist, and whether the customiser offers each one. At least one entry must be active. A part may be listed once.","example":[[]],"items":{"$ref":"#/components/schemas/CustomiserLetterTypeLetterPart"}},"CustomiserLetterTypeLetterPart":{"type":"object","properties":{"letter_part":{"$ref":"#/components/schemas/CustomiserExtraYesColourExclusionLetterPart"},"active":{"type":"boolean","description":"Whether the customiser offers this part.","example":true}},"required":["letter_part","active"]},"LetterTypeValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LetterTypeValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LetterTypeValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"LetterTypeReorderRequest":{"type":"object","properties":{"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["letter_type_ids"]},"CustomiserLetterTypeOneOf":{"description":"Unsafe legacy colour selection","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"reason":{"type":"string"},"letter_part":{"type":"string"},"letter_type_id":{"type":"integer"},"request_id":{"type":"string"}}},"CustomiserLetterTypeOneOf2":{"description":"Unsafe unknown legacy colour selection","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"reason":{"type":"string"},"letter_part":{"$ref":"#/components/schemas/NullableString"},"letter_type_id":{"type":"integer"},"request_id":{"type":"string"}}},"LetterTypeValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/LetterTypeValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"LetterTypeValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse8":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData8"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData8":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserMaterialResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMaterialData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserMaterialData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserMaterialItem"}},"CustomiserMaterialItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"excluded_backboard_ids":{"type":"array"},"excluded_mounting_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserMaterialResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMaterialItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MaterialValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MaterialValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MaterialValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"material_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"MaterialReorderRequest":{"type":"object","properties":{"material_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["material_ids"]},"MaterialValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MaterialValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MaterialValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"material_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse9":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData9"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData9":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"material_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserMountingColourResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMountingColourData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserMountingColourData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserMountingColourItem"}},"CustomiserMountingColourItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"hexcode":{"type":"string"},"opacity":{"type":"integer"},"applied_to_visualisation":{"type":"boolean"},"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserMountingColourResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMountingColourItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingColourValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MountingColourValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingColourValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_colour_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"MountingColourReorderRequest":{"type":"object","properties":{"mounting_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["mounting_colour_ids"]},"MountingColourValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MountingColourValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingColourValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_colour_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse10":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData10"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData10":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_colour_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserMountingResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMountingData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserMountingData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserMountingItem"}},"CustomiserMountingItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"visualisation":{"type":"string"},"pedestal_pole_width_pct":{"$ref":"#/components/schemas/NullableString"},"pedestal_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_post_height_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_inset_pct":{"$ref":"#/components/schemas/NullableString"},"letter_parts":{"type":"array"},"excluded_colour_ids":{"type":"array"},"excluded_backboard_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserMountingResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserMountingItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MountingValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"MountingReorderRequest":{"type":"object","properties":{"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["mounting_ids"]},"MountingValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MountingValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"MountingValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse11":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData11"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData11":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"mounting_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserPresetResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserPresetData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserPresetData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"code":{"type":"string"},"design":{"$ref":"#/components/schemas/CustomiserPresetDesign"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserPresetDesign":{"type":"object","properties":{"colour":{"type":"integer"},"font":{"type":"integer"},"text":{"type":"string"}}},"CustomiserPresetRequest":{"type":"object","properties":{"name":{"type":"string","description":"The preset's display name.","example":"Neon script in pink"},"design":{"$ref":"#/components/schemas/CustomiserProductCart"}},"required":["name","design"]},"PresetValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PresetValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"PresetValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"preset_id":{"type":"integer"},"valid":{"type":"boolean"}}},"PresetValidateDeleteResponse2":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf2"},{"$ref":"#/components/schemas/CustomiserOneOf"}],"required":["type","title","status","code","detail","request_id"]},"CustomiserPricingModel":{"type":"string","description":"The customiser's pricing model. Closed set.","enum":["simple_letter","advanced_letter","frame_fit","legacy_fixed_height"]},"NullableInteger":{"type":["integer","null"],"description":"Nullable comparison price in minor units; when supplied it cannot be lower than price_amount.","example":14900},"CustomiserCustomiser":{"type":"object","properties":{"customiser_id":{"type":"integer"},"name":{"type":"string"},"price_type":{"type":"string"},"product":{"type":"string"},"selection_type":{"type":"string"},"active":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"active":{"type":"boolean"},"pricing_model":{"type":"string"},"pricing_model_display_name":{"type":"string"},"sign_category":{"type":"string"},"product_family":{"type":"string"},"selection_mode":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserPagination":{"type":"object","properties":{"has_more":{"type":"boolean","description":"Whether more customisers exist after this page. When true, follow links.next (or pass pagination.next_cursor as the cursor parameter) to fetch the next page."},"next_cursor":{"$ref":"#/components/schemas/NullableString"}},"required":["has_more","next_cursor"]},"CustomiserOneOfErrors2":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserOneOfError2"}},"CustomiserOneOfError2":{"type":"object","properties":{"parameter":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"}}},"CustomiserResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserRequest":{"type":"object","properties":{"prototype":{"type":"string","description":"The prototype id to create from, from the list customiser prototypes operation.","example":"neon-frame-fit"},"name":{"type":"string","description":"The customiser's name. Defaults to the prototype's own name.","example":"Neon sign builder"}},"required":["prototype"]},"CustomiserResponse2":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors2"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserRequest2":{"type":"object","properties":{"name":{"type":"string","description":"The customiser's name.","example":"Neon sign builder"},"selection_mode":{"type":"string","description":"How shoppers style the sign text: single_style or per_word.","example":"per_word"}}},"CustomiserResponse3":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"referenced_by":{"$ref":"#/components/schemas/CustomiserBackboardReferencedBy"},"referenced_by_count":{"type":"integer"},"order_line_item_count":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]},"CustomiserSettingSelectionOrder":{"type":"object","properties":{"family":{"type":"string"},"label":{"type":"string"},"hidden":{"type":"boolean"}}},"CustomiserSizResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserSizData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserSizData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserSizItem"}},"CustomiserSizItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"char_limit":{"type":"integer"},"line_limit":{"type":"integer"},"min_char":{"type":"integer"},"width_cm":{"type":"integer"},"height_cm":{"$ref":"#/components/schemas/NullableString"},"line_height_cm":{"type":"integer"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"width_multiplier":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"type":"string"},"height_measurement_mode":{"type":"string"},"border_margin_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"SizValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SizValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"SizValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"size_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"SizReorderRequest":{"type":"object","properties":{"size_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["size_ids"]},"SizValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SizValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"SizValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"size_id":{"type":"integer"},"valid":{"type":"boolean"}}},"CustomiserSupportFinishResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserSupportFinishData"},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserSupportFinishData":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserSupportFinishItem"}},"CustomiserSupportFinishItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"finish_kind":{"type":"string"},"hexcode":{"type":"string"},"opacity":{"type":"integer"},"applied_to_visualisation":{"type":"boolean"},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"texture_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"CustomiserSupportFinishResponse2":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserSupportFinishItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserSupportFinishFinishKind":{"type":"string","description":"What the finish is made of. Defaults to colour; texture needs an already-uploaded texture image.","example":"colour","enum":["colour","texture"]},"SupportFinishValidateCreateResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SupportFinishValidateCreateData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"SupportFinishValidateCreateData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"support_finish_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"SupportFinishReorderRequest":{"type":"object","properties":{"support_finish_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["support_finish_ids"]},"SupportFinishValidateDeleteResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SupportFinishValidateDeleteData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"SupportFinishValidateDeleteData":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"support_finish_id":{"type":"integer"},"valid":{"type":"boolean"}}},"ImageValidateDeleteResponse12":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ImageValidateDeleteData12"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"ImageValidateDeleteData12":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"support_finish_id":{"type":"integer"},"image_slot":{"type":"string"},"valid":{"type":"boolean"}}},"CustomiserProductResponse":{"type":"object","properties":{"ok":{"type":"boolean"}}},"CustomiserProductProductTypePaths":{"type":["object","null"],"description":"Fixed-width base price by line. For material length and frame fit this field is a number.","example":{"line_1":1000,"line_2":1200,"line_3":1400},"properties":{}},"OrderParameter":{"type":"string","description":"Order acceptance status. This set is expected to grow; tolerate unknown response values.","enum":["received"]},"OrderResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrderData"},"links":{"$ref":"#/components/schemas/CustomiserProductLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","links","meta"]},"OrderData":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"store_order_id":{"type":"string"},"store_order_number":{"type":"string"},"status":{"$ref":"#/components/schemas/OrderParameter"},"status_display_name":{"type":"string"},"total_amount":{"type":"integer"},"currency":{"$ref":"#/components/schemas/NullableString"},"shipping_method":{"$ref":"#/components/schemas/NullableString"},"customer":{"$ref":"#/components/schemas/OrderCustomer"},"billing_address":{"$ref":"#/components/schemas/OrderBillingAddress"},"shipping_address":{"$ref":"#/components/schemas/OrderBillingAddress"},"line_items":{"$ref":"#/components/schemas/OrderLineItems"},"created_at":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"$ref":"#/components/schemas/NullableString"}},"required":["object","id","store_order_id","store_order_number","status","status_display_name","total_amount","currency","shipping_method","customer","billing_address","shipping_address","line_items","created_at","updated_at"]},"OrderCustomer":{"type":["object","null"],"properties":{"first_name":{"$ref":"#/components/schemas/NullableString"},"last_name":{"$ref":"#/components/schemas/NullableString"},"email":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"}},"required":["first_name","last_name","email","phone"]},"OrderBillingAddress":{"type":["object","null"],"properties":{"first_name":{"$ref":"#/components/schemas/NullableString"},"last_name":{"$ref":"#/components/schemas/NullableString"},"company":{"$ref":"#/components/schemas/NullableString"},"address_1":{"$ref":"#/components/schemas/NullableString"},"address_2":{"$ref":"#/components/schemas/NullableString"},"city":{"$ref":"#/components/schemas/NullableString"},"province":{"$ref":"#/components/schemas/NullableString"},"postcode":{"$ref":"#/components/schemas/NullableString"},"country":{"$ref":"#/components/schemas/NullableString"},"email":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"}},"required":["first_name","last_name","company","address_1","address_2","city","province","postcode","country","email","phone"]},"OrderLineItems":{"type":"array","items":{"$ref":"#/components/schemas/OrderLineItem"}},"OrderLineItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"store_product_id":{"$ref":"#/components/schemas/NullableString"},"customiser_id":{"type":"integer"},"title":{"$ref":"#/components/schemas/NullableString"},"quantity":{"type":"integer"},"unit_price_amount":{"type":"integer"},"currency":{"$ref":"#/components/schemas/NullableString"},"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"}},"required":["object","id","store_product_id","customiser_id","title","quantity","unit_price_amount","currency","preview_image_url","width_cm","height_cm","length_cm"]},"CustomiserProductLinks":{"type":"object","properties":{"self":{"type":"string"},"documentation":{"type":"string"}},"required":["self","documentation"]},"CustomiserProductMeta":{"type":"object","properties":{"api_version":{"type":"string"},"request_id":{"type":"string"}},"required":["api_version","request_id"]},"OrderBillingAddress2":{"type":["object","null"],"description":"Nullable billing address snapshot.","example":{"first_name":"Ada","last_name":"Lovelace","address_1":"1 Example Street","city":"Adelaide","province":"SA","postcode":"5000","country":"AU"},"properties":{"first_name":{"$ref":"#/components/schemas/NullableString"},"last_name":{"$ref":"#/components/schemas/NullableString"},"company":{"$ref":"#/components/schemas/NullableString"},"address_1":{"$ref":"#/components/schemas/NullableString"},"address_2":{"$ref":"#/components/schemas/NullableString"},"city":{"$ref":"#/components/schemas/NullableString"},"province":{"$ref":"#/components/schemas/NullableString"},"postcode":{"$ref":"#/components/schemas/NullableString"},"country":{"$ref":"#/components/schemas/NullableString"},"email":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"}},"additionalProperties":false},"CustomiserPricingResponse":{"type":"object","properties":{"pricing":{"$ref":"#/components/schemas/CustomiserPricingPricing"}}},"CustomiserPricingPricing":{"type":"object","properties":{"customiser_id":{"type":"integer"},"pricing_model":{"type":"string"},"value_units":{"$ref":"#/components/schemas/CustomiserPricingPricingValueUnits"},"sizes":{"type":"array"},"base_pricing":{"$ref":"#/components/schemas/CustomiserPricingPricingBasePricing"},"price_lists":{"type":"array"}}},"CustomiserPricingPricingValueUnits":{"type":"object","properties":{"base_price":{"type":"string"},"letter_price":{"type":"string"},"letter_price_by_position":{"type":"string"},"price_per_letter":{"type":"string"},"price_per_letter_by_position":{"type":"string"},"price_per_cm":{"type":"string"},"price_per_sq_cm":{"type":"string"}}},"CustomiserPricingPricingBasePricing":{"type":"object","properties":{"mode":{"type":"string"},"rows":{"type":"array"}}},"CustomiserPricingResponse2":{"type":"object","properties":{"message":{"type":"string"}}},"CustomiserPricingOneOfObject":{"type":"string","description":"The resource type discriminator. Always \"customiser_pricing\" for this endpoint (closed set).","enum":["customiser_pricing"]},"CustomiserPricingOneOfPricingModel":{"type":"string","description":"How the customiser prices signs (closed set for this document): simple_letter, advanced_letter or frame_fit. The document's shape follows this value. Read-only: pricing models cannot be switched through this endpoint, and customisers on the retired fixed-height model return a legacy_pricing_model problem instead of a document.","enum":["simple_letter","advanced_letter","frame_fit"]},"CustomiserPricingOneOfSizes":{"type":"array","description":"The customiser's sizes in display order. Every size reports the same key set; fields that do not apply to the pricing model are null. Common fields: size_id (integer), client_key (write-side correlation key, always null on reads), name, description, sort_order, min_char. simple_letter sizes use char_limit, line_limit, width_cm, line_height_cm, sizing_strategy (closed set: fixed_width, fixed_height — how the sign scales; internal legacy spellings never appear) and height_measurement_mode (closed set: line_height, letter_height). advanced_letter sizes use width_multiplier. frame_fit sizes use width_cm, height_cm, char_limit, min_char and length_cm, where length_cm is physical depth. Dimensions are centimetres.","items":{"$ref":"#/components/schemas/CustomiserPricingOneOfSiz"}},"CustomiserPricingOneOfSiz":{"type":"object","properties":{"size_id":{"type":"integer"},"client_key":{"$ref":"#/components/schemas/NullableString"},"name":{"type":"string"},"description":{"type":"string"},"sort_order":{"type":"integer"},"char_limit":{"type":"integer"},"line_limit":{"type":"integer"},"min_char":{"type":"integer"},"width_cm":{"type":"integer"},"height_cm":{"$ref":"#/components/schemas/NullableString"},"line_height_cm":{"type":"integer"},"width_multiplier":{"$ref":"#/components/schemas/NullableString"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"type":"string"},"height_measurement_mode":{"type":"string"}}},"CustomiserPricingOneOfBasePricingMode":{"type":"string","description":"The base pricing mode (closed set): included_in_price_lists, single_base_price_by_size (simple_letter only), single_base_price_by_size_boundary (advanced_letter and frame_fit only).","enum":["included_in_price_lists","single_base_price_by_size","single_base_price_by_size_boundary"]},"CustomiserPricingBasePricingRowBasePrice":{"type":"object","properties":{"line_1":{"type":"integer"},"line_2":{"type":"integer"},"line_3":{"type":"integer"}}},"NullableObject":{"additionalProperties":{"$ref":"#/components/schemas/CustomiserPricingRowLetterPriceByPositionAdditionalProperties"},"description":"simple_letter: optional per-line position schedules keyed line_1..line_10. Each list contains 1-100 non-negative integer minor-unit amounts; null clears a saved schedule.","example":{"line_1":[0,100],"line_2":[0,120],"line_3":[0,140]},"propertyNames":{"$ref":"#/components/schemas/CustomiserPricingRowLetterPriceByPositionPropertyNames"},"type":["object","null"]},"CustomiserPricingRowLetterPriceByPositionAdditionalProperties":{"items":{"$ref":"#/components/schemas/Integer"},"maxItems":100,"minItems":1,"type":"array"},"Integer":{"minimum":0,"type":"integer"},"CustomiserPricingRowLetterPriceByPositionPropertyNames":{"pattern":"^line_(?:[1-9]|10)$"},"CustomiserPricingOneOfBasePricing":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/CustomiserPricingOneOfBasePricingMode"},"rows":{"type":"array"}},"required":["mode"],"description":"The base price configuration. mode is a closed set: included_in_price_lists (base prices live inside price list rows; rows is empty), single_base_price_by_size (simple_letter only: one base price per size, rows carry size_id/size_key and per-line base_price), or single_base_price_by_size_boundary (advanced_letter and frame_fit: rows carry base_price in minor units with width_limit_cm/height_limit_cm boundaries)."},"CustomiserPricingPriceListRowPricePerLetterByPosition2":{"description":"advanced_letter fixed_letter: optional 1-100 item position schedule in non-negative integer minor units; null clears it.","example":[0,100,125],"items":{"$ref":"#/components/schemas/Integer"},"maxItems":100,"minItems":1,"type":["array","null"]},"CustomiserPricingResponse3":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserPricingData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},"CustomiserPricingData":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"pricing_model":{"type":"string"},"pricing_model_display_name":{"type":"string"},"currency":{"type":"string"},"sizes":{"$ref":"#/components/schemas/CustomiserPricingOneOfSizes"},"base_pricing":{"$ref":"#/components/schemas/CustomiserPricingPricingBasePricing"},"price_lists":{"$ref":"#/components/schemas/CustomiserPricingPriceLists"},"sheet_pricing":{"$ref":"#/components/schemas/NullableString"}}},"CustomiserPricingPriceLists":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserPricingPriceList"}},"CustomiserPricingPriceList":{"type":"object","properties":{"price_list_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"rows":{"$ref":"#/components/schemas/CustomiserPricingPriceListRows"}}},"CustomiserPricingPriceListRows":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserPricingPriceListRow"}},"CustomiserPricingPriceListRow":{"type":"object","properties":{"size_id":{"type":"integer"},"size_key":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice"},"letter_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice"},"letter_price_by_position":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowLetterPriceByPosition"}}},"CustomiserPricingPriceListRowLetterPriceByPosition":{"type":"object","properties":{"line_1":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"line_2":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"line_3":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}}},"CustomiserPricingBasePricingRowBasePrice2":{"description":"A non-negative integer minor-unit amount for Advanced Letter and Frame Fit, or a Simple Letter per-line amount map keyed line_1..line_10.","example":{"line_1":1000,"line_2":1200,"line_3":1400},"oneOf":[{"$ref":"#/components/schemas/Integer"},{"$ref":"#/components/schemas/Object"}]},"Object":{"additionalProperties":{"$ref":"#/components/schemas/Integer"},"description":"A non-negative integer minor-unit amount for Advanced Letter and Frame Fit, or a Simple Letter per-line amount map keyed line_1..line_10.","example":{"line_1":1000,"line_2":1200,"line_3":1400},"propertyNames":{"$ref":"#/components/schemas/CustomiserPricingRowLetterPriceByPositionPropertyNames"},"type":"object"},"ProductSelectionBacklight":{"type":["object","null"],"properties":{"value":{"type":"integer"},"display_name":{"$ref":"#/components/schemas/NullableString"}},"required":["value","display_name"]},"ProductSelectionColours":{"type":"array","items":{"$ref":"#/components/schemas/ProductSelectionColour"}},"ProductSelectionColour":{"type":"object","properties":{"value":{"type":"integer"},"display_name":{"$ref":"#/components/schemas/NullableString"}},"required":["value","display_name"]},"QuoteItemFileKind":{"type":"string","description":"What the file is (closed set): a shopper upload, a rendered preview of the configured sign, or an artefact the customiser generated.","enum":["customer_upload","product_preview","generated_design"]},"QuoteItemAdClickIds":{"type":"object","properties":{"gclid":{"type":"string"}}},"QuoteItemUtmParams":{"type":"object","properties":{"utm_source":{"type":"string"},"utm_medium":{"type":"string"}}},"UploadRequest":{"type":"object","properties":{"purpose":{"$ref":"#/components/schemas/UploadPurpose"},"filename":{"type":"string","description":"The display filename, at most 255 characters. Metadata only: the storage key is always generated by the server, so this never affects where the file is stored. Its extension must be one the purpose accepts.","example":"Signature.ttf"},"content_type":{"type":"string","description":"The media type of the file you are about to send. Must be one the purpose accepts.","example":"font/ttf"},"byte_size":{"type":"integer","description":"The file's size in bytes. Must not exceed the purpose's cap; the actual bytes are checked again at attach.","example":184320}},"required":["purpose","filename","content_type","byte_size"]},"UploadPurpose":{"type":"string","description":"What the file is for. Closed set, and it must be the purpose of the slot you intend to attach to: `font_ttf` for a font's typeface, `option_image` for an option row's swatch, example or button image, `option_texture_image` for the texture a custom_texture finish renders from, `backboard_shape_svg` for a backboard's custom shape, `label_image` for the labels document's product image. The accepted file types and the size cap follow from it.","example":"font_ttf","enum":["font_ttf","option_image","option_texture_image","backboard_shape_svg","label_image"]},"WebhookWebhook":{"type":"object","properties":{"webhook_id":{"type":"integer"},"topic":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"},"secret":{"type":"string"},"meta":{"$ref":"#/components/schemas/CustomiserProductCart"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"WebhookResponse":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/WebhookWebhook"}}},"WebhookRequest":{"type":"object","properties":{"topic":{"type":"string","description":"The event topic to subscribe to. Must be one of: product:created, order:created, form:submitted.","example":"product:created"},"url":{"type":"string","description":"The URL where webhook payloads will be sent. Must be a valid URL.","example":"https://example.com/webhook"},"meta":{"$ref":"#/components/schemas/WebhookMeta"}},"required":["topic","url"]},"WebhookMeta":{"type":["object","null"],"description":"Optional metadata to store with the webhook.","example":[],"properties":{"source":{"type":"string","example":null}}},"WebhookExampleOneOfOrderBillingAddress":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"address_1":{"type":"string"},"address_2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"postcode":{"type":"string"},"country":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"WebhookSubscriptionItem":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"topic":{"$ref":"#/components/schemas/WebhookEventTypeItemTopic"},"url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["object","id","topic","url","created_at","updated_at"]},"WebhookEventTypeItemTopic":{"type":"string","description":"The event topic. This set is expected to grow; tolerate unknown values returned by read operations.","example":"product:created","enum":["product:created","order:created","form:submitted"]},"WebhookEventTypeLinks":{"type":"object","properties":{"self":{"type":"string"},"documentation":{"type":"string"},"next":{"$ref":"#/components/schemas/NullableString"}},"required":["self","documentation","next"]},"WebhookSubscriptionResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionItem"},"links":{"$ref":"#/components/schemas/CustomiserProductLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","links","meta"]}}},"security":[{"default":[]}],"paths":{"/api/v3/analytics/summary":{"parameters":[],"get":{"summary":"Get an analytics summary\n\nReturn bounded operational totals for the authenticated Store. Orders,\nmodern products, and native-currency revenue are counted over complete UTC\ncalendar days. Revenue remains in integer minor units and is never\nconverted or combined across currencies. Malformed stored currency codes\nappear honestly in one null-currency bucket. Customer details, provider\ndata, and legacy ProductCache rows are excluded. Requires\n`analytics:read`; this operation is read-only and safe to retry.\n\n`end_date` is the inclusive final day and must be before the current UTC\ndate. It defaults to yesterday. `period` defaults to `30d`. The response\nreports the half-open query bounds `[starts_at, ends_before)`. Correct a\n422 using the reported parameter, replace a key after 401, add the scope\nafter 403, and retry 429 responses after `retry_after` seconds.","operationId":"getAnalyticsSummary","parameters":[{"in":"query","name":"period","description":"Closed set of supported complete-day reporting periods.","example":"30d","required":false,"schema":{"$ref":"#/components/schemas/AnalyticsSummaryParameter"}},{"in":"query","name":"end_date","description":"Inclusive final UTC date in YYYY-MM-DD format. Defaults to the previous UTC day.","example":"2026-07-19","required":false,"schema":{"type":"string","description":"Inclusive final UTC date in YYYY-MM-DD format. Defaults to the previous UTC day."}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"period":{"$ref":"#/components/schemas/AnalyticsSummaryParameter"},"starts_at":{"type":"string"},"ends_before":{"type":"string"},"order_count":{"type":"integer"},"product_count":{"type":"integer"},"revenue":{"type":"array","items":{"type":"object","properties":{"currency":{"$ref":"#/components/schemas/NullableString"},"amount":{"type":"integer"},"order_count":{"type":"integer"}},"required":["currency","amount","order_count"]}}},"required":["object","period","starts_at","ends_before","order_count","product_count","revenue"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"422":{"description":"Invalid reporting period","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/AnalyticsSummaryResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Analytics"],"security":[{"default":["analytics:read"]}]}},"/api/v2/orders":{"parameters":[],"post":{"summary":"Create an order\n\nStep 2 of the custom integration flow. After your server handles the `product:created`\ncallback and returns an `external_id`, send the completed order from your\necommerce platform. Each `products[].external_id` value must match a product ID\nyou previously returned during product creation.","operationId":"createAnOrderStep2OfTheCustomIntegrationFlowAfterYourServerHandlesTheproductcreatedCallbackAndReturnsAnexternalIdSendTheCompletedOrderFromYourEcommercePlatformEachproductsexternalIdValueMustMatchAProductIDYouPreviouslyReturnedDuringProductCreation","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","properties":{"order_id":{"type":"integer"},"external_id":{"type":"string"},"external_order_number":{"type":"string"},"currency":{"type":"string"},"shipping_line":{"type":"string"},"products":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string"},"quantity":{"type":"integer"},"price":{"type":"integer"}}}}}}}}}}}},"tags":["Custom integrations"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"integration_id":{"type":"integer","description":"The Sign Customiser integration ID that owns the order.","example":42},"external_id":{"type":"string","description":"Your platform's internal order ID.","example":"order-123456"},"external_order_number":{"type":"string","description":"The human-readable order number shown to customers.","example":"SC-1001"},"currency":{"$ref":"#/components/schemas/NullableString"},"billing_address":{"type":"object","description":"The billing address for the order.","example":[],"properties":{"first_name":{"type":"string","description":"The billing first name.","example":"John"},"last_name":{"type":"string","description":"The billing last name.","example":"Smith"},"email":{"type":"string","description":"The billing email address.","example":"customer@example.com"},"phone":{"$ref":"#/components/schemas/NullableString"},"address_1":{"type":"string","description":"The first billing address line.","example":"123 Main St"},"address_2":{"$ref":"#/components/schemas/NullableString"},"city":{"type":"string","description":"The billing city.","example":"Los Angeles"},"province":{"type":"string","description":"The billing province or state.","example":"CA"},"postcode":{"type":"string","description":"The billing postcode or ZIP code.","example":"90001"},"country":{"type":"string","description":"The billing country code.","example":"US"}}},"shipping_address":{"type":"object","description":"The shipping address for the order.","example":[],"properties":{"first_name":{"type":"string","description":"The shipping first name.","example":"John"},"last_name":{"type":"string","description":"The shipping last name.","example":"Smith"},"address_1":{"type":"string","description":"The first shipping address line.","example":"123 Main St"},"address_2":{"$ref":"#/components/schemas/NullableString"},"city":{"type":"string","description":"The shipping city.","example":"Los Angeles"},"province":{"type":"string","description":"The shipping province or state.","example":"CA"},"postcode":{"type":"string","description":"The shipping postcode or ZIP code.","example":"90001"},"country":{"type":"string","description":"The shipping country code.","example":"US"}}},"shipping_line":{"type":"string","description":"The selected shipping method.","example":"Standard Shipping"},"customer":{"type":"object","description":"Customer details for the order.","example":[],"properties":{"first_name":{"$ref":"#/components/schemas/NullableString"},"last_name":{"$ref":"#/components/schemas/NullableString"},"email":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"}}},"products":{"$ref":"#/components/schemas/OrderProducts"}},"required":["integration_id","external_id","external_order_number","products"]}}}}}},"/api/v3/customisers/{customiser_id}/backboards":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List backboards\n\nLists the customiser's backboard options in the order the storefront\npresents them (`sort_order`, then id). `base_price` is integer minor\nunits of `currency`; `price_per_sq_cm` is a decimal major-unit rate, the\nsame units the pricing document uses. Image fields and the whole\n`geometry` object are read-only. Requires `customisers:read`. Follow\n`links.next` while `pagination.has_more` is true, sending each cursor\nback unchanged. The operation is read-only and always safe to retry.","operationId":"listBackboards","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserBackboardItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a backboard\n\nAdds a backboard to the customiser, at the end of the current\npresentation order. Only `name` is required; every other field has a\ndocumented default (`backboard_type` null, `letter_type_ids` empty,\n`allow_multilayer` false, `base_price` 0, `price_multiplier` 1,\n`price_multiplier_target` base_letter_price). On a channel customiser\noutside the cut-out family, `letter_type_ids` is required and must name\nat least one of the customiser's own letter types, because a backboard no\nletter type offers is unreachable in the storefront.\n\nImage fields, the uploaded shape and everything derived from it\n(`geometry`, `custom_shape`, `padding`), and the multilayer `layers`\ndefinitions are read-only here, and `sort_order` moves only through the\nreorder operation — naming any of them is a 422. The shape has its own\nslot, `PUT .../backboards/{backboard_id}/shape`. `backboard_type` cannot be set to `custom_shape` or\n`cutout` on a create: those draw the board from an uploaded shape, and a\ncreate has no row to attach one to yet. Create the backboard, attach its\nshape with `PUT .../backboards/{backboard_id}/shape`, then set the type.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createBackboard","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserBackboardData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Shape upload required","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"backboard_type":{"type":"string","description":"The construction the backboard uses. Defaults to none. The shape-owned types custom_shape and cutout are accepted only on a backboard that already carries a shape, attached through the shape slot.","example":"board","enum":["board","box","stand","single_raceway","double_raceway","letter_rail","cut_to_shape","cut_to_letter","custom_shape","cutout"]},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The backboard's display name.","example":"Clear Acrylic Panel"},"description":{"type":"string","description":"A description shown alongside the option.","example":"A 5mm clear acrylic panel"},"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"outline_size":{"type":"number","description":"How far a cut-to-letter backboard extends beyond the letters, as a relative size.","example":8},"allow_multilayer":{"type":"boolean","description":"Whether the backboard offers its configured layers.","example":false},"custom_background_enabled":{"type":"boolean","description":"Whether customers may supply their own background artwork.","example":false},"show_custom_background":{"type":"boolean","description":"Whether that artwork renders in the preview.","example":false},"base_price":{"type":"integer","description":"A flat amount added when the backboard is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the backboard is chosen.","example":1.2},"price_per_sq_cm":{"type":"number","description":"An area rate in decimal major currency units per square centimetre.","example":0.022}},"required":["name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateBackboard","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder backboards\n\nSets the storefront presentation order of the customiser's backboards.\n`backboard_ids` must be the complete current membership of the family,\neach id exactly once, in the order to present them; a partial or stale\nlist is refused with a 422 naming the missing, unknown, and duplicated\nids rather than being partially applied. Competing reorders of unchanged\nmembership are last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderBackboards","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserBackboardData"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderBackboards","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id, from the list backboards operation.","example":"11","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a backboard\n\nReturns one of the customiser's backboards. Requires `customisers:read`\nand is safe to retry. A backboard belonging to another customiser or\nanother store is indistinguishable from an unknown id.","operationId":"getBackboard","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserBackboardItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a backboard\n\nApplies an RFC 7396 JSON Merge Patch to one backboard: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`, `backboard_type`, `outline_size`, `price_per_sq_cm`).\nArrays are replaced wholesale, so sending `letter_type_ids` replaces the\nwhole assignment. Read-only and unknown fields are rejected. An empty\nobject `{}` is a valid no-op. Requires `customisers:write`.\n\n`backboard_type` may only become `custom_shape` or `cutout` on a\nbackboard that already carries an uploaded shape, so attach one through\n`PUT .../backboards/{backboard_id}/shape` first. `custom_shape` is\nadditionally refused while the customiser has a border margin configured,\nbecause a shape owns its own proportions. Clear the border margins first.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateBackboard","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"backboard_type":{"type":"string"},"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"outline_size":{"$ref":"#/components/schemas/NullableString"},"allow_multilayer":{"type":"boolean"},"custom_background_enabled":{"type":"boolean"},"show_custom_background":{"type":"boolean"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"price_per_sq_cm":{"type":"number"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"geometry":{"$ref":"#/components/schemas/CustomiserBackboardItemGeometry"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The backboard's display name.","example":"Clear Acrylic Panel"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"A 5mm clear acrylic panel"},"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the backboard is chosen, in integer minor units.","example":500},"price_per_sq_cm":{"type":"number","description":"An area rate in decimal major currency units per square centimetre; null clears it.","example":0.022}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a backboard\n\nRemoves the backboard and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA backboard anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — support finishes that\noffer it, materials and mountings that exclude it, and saved presets that\nchose it — and references are never silently cleared. Requires\n`customisers:write` and an `Idempotency-Key`; retrying the same request\nand key replays the original receipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteBackboard","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["backboard_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserBackboardData"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Backboard still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateBackboard","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShapeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard delete\n\nDry-runs the delete without removing anything: an in-use backboard\nreturns the identical 409 `resource_in_use` problem the delete would\nreturn, and a deletable one returns a 200 verdict. Nothing is written and\nno idempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteBackboard","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShapeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Backboard still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the backboard's images to write (closed set): the artwork shown for the panel, or the smaller picture on the button that selects it.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a backboard image\n\nTakes the raster image of a staged upload and puts it in one of the\nbackboard's two image slots, returning the backboard with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachBackboardImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"backboard_type":{"type":"string"},"letter_type_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"outline_size":{"$ref":"#/components/schemas/NullableString"},"allow_multilayer":{"type":"boolean"},"custom_background_enabled":{"type":"boolean"},"show_custom_background":{"type":"boolean"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"image_url":{"type":"string"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"geometry":{"$ref":"#/components/schemas/CustomiserBackboardItemGeometry"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a backboard image\n\nClears one of the backboard's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the backboard as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteBackboardImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BackboardImageSlot"},"customiser_id":{"type":"integer"},"backboard_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserBackboardItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a backboard image attach\n\nDry-runs the attach: the slot must exist on this backboard, the upload\nmust exist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachBackboardImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a backboard image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteBackboardImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/shape":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}}],"put":{"summary":"Attach a backboard shape\n\nTakes the SVG of a staged upload and makes it this backboard's shape,\nreturning the backboard with `geometry.has_custom_shape` true and\n`geometry.status` `pending`. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` using purpose\n`backboard_shape_svg`, send the bytes to the presigned target it returns,\nthen send that upload's id here. The upload is single use: attaching it a\nsecond time answers 409, and a replacement shape needs a new upload.\n\nA shape is the one uploaded file this API stores inside its own resource\nrather than as a file. It is sanitised and written into the backboard, is\nnever published as a URL, and the staged object is deleted rather than\nkept. `geometry` is how you read it back.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, be well-formed XML with an `svg` element, carry\nno script elements, event-handler attributes or `javascript:` references,\nand still draw something once sanitised. A file that fails answers\n`invalid_svg` and leaves the upload `rejected`, and attaching it again\nreplays that code — so fix the file and create a new upload rather than\nretrying.\n\nAttaching a shape queues server-side geometry measurement, which is why\nthe response reports `geometry.status` `pending`. Poll the backboard until\nit reaches `ready`; `failed` carries a `geometry.failure_reason`. A\nbackboard that has measured is what `backboard_type` `custom_shape` and\n`cutout` need, so this is the call that makes those types usable: attach\nthe shape, then set the type in the ordinary backboard write.\n\nSending this to a backboard that already has a shape replaces it and\ndiscards the previous measurement. Re-attaching a document that sanitises\nto the shape already measured is the one exception: there is nothing new\nto measure, so the existing `ready` result stands and the response says so\nrather than reporting `pending`.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachBackboardShape","parameters":[{"in":"header","name":"Idempotency-Key","example":"e4a2b1c9-7d3f-4a6b-9c1e-2f3a4b5c6d7e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"backboard_type":{"type":"string"},"letter_type_ids":{"type":"array"},"outline_size":{"$ref":"#/components/schemas/NullableString"},"allow_multilayer":{"type":"boolean"},"custom_background_enabled":{"type":"boolean"},"show_custom_background":{"type":"boolean"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"geometry":{"type":"object","properties":{"has_custom_shape":{"type":"boolean"},"status":{"type":"string","description":"`pending` after an attach, because measurement runs asynchronously on the new shape; read the backboard again to see it reach `ready` or `failed`. It stays `ready` when the attached document sanitises to the shape that was already measured, since that measurement still describes it."},"failure_reason":{"$ref":"#/components/schemas/NullableString"},"measured_at":{"$ref":"#/components/schemas/NullableString"}},"required":["status"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unusable SVG","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a backboard shape\n\nRemoves this backboard's shape and everything measured from it, returning\na receipt and the backboard as it now stands. Requires `customisers:write`\nand an `Idempotency-Key`.\n\nThe shape lives inside the backboard rather than in storage, so\n`removed_file` carries no filename and no URL: an inline shape was never\npublished at one. `geometry` on the returned resource is the state that\nmatters — `has_custom_shape` false and every measurement field null.\n\nA `custom_shape` or `cutout` backboard is drawn from its shape, so\ndeleting one is refused with a 422 on `/backboard_type` while the\nbackboard is either of those types. Change `backboard_type` first, then\ndelete the shape. Deleting a shape a backboard does not have answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteBackboardShape","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"slot":{"type":"string"},"customiser_id":{"type":"integer"},"backboard_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"type":"object","properties":{"filename":{"$ref":"#/components/schemas/NullableString"},"url":{"$ref":"#/components/schemas/NullableString"}},"description":"What came out of the slot. Both members are null for a shape: it was stored inside the backboard rather than as a file, so it had no filename beside it and was never published at a URL."},"resource":{"$ref":"#/components/schemas/CustomiserBackboardData"}},"required":["removed_file","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No shape on the backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Shape-owned backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/shape/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard shape attach\n\nDry-runs the attach: the upload must exist for this store and this API\nkey, and its purpose must be `backboard_shape_svg`. Nothing is written,\nthe upload is not consumed, and no idempotency key is spent. Requires\n`customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the SVG is usable — that verdict only exists once the attach reads\nthe object, and reading it here would let a preflight mark a good upload\nrejected. Treat a valid verdict as \"the upload is attachable\", not \"the\nshape is sound\".","operationId":"validateAttachBackboardShape","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShapeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backboard or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backboards/{backboard_id}/shape/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backboard_id","description":"The backboard id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backboard shape delete\n\nDry-runs the delete: a backboard with no shape answers the identical 404\nthe delete would, a shape-owned backboard answers the identical 422, and\nanything else returns a 200 verdict. Nothing is written, nothing is\nremoved, and no idempotency key is consumed. Requires `customisers:write`.","operationId":"validateDeleteBackboardShape","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShapeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No shape on the backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Shape-owned backboard","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backboards"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List backlights\n\nLists the customiser's backlight options in the order the storefront\npresents them (`sort_order`, then id). `base_price` is integer minor\nunits of `currency`; minimum sizes are centimetres; image fields are\nread-only public URLs. Requires `customisers:read`. Follow `links.next`\nwhile `pagination.has_more` is true, sending each cursor back unchanged.\nThe operation is read-only and always safe to retry.","operationId":"listBacklights","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a backlight\n\nAdds a backlight to the customiser, at the end of the current\npresentation order. Only `name` is required; every other field has a\ndocumented default (`colour_type` single, `texture` flat, `min_size_rule`\nany, `base_price` 0, `price_multiplier` 1, `price_multiplier_target`\nbase_letter_price). Image fields are read-only until the upload surface\nships, and `sort_order` moves only through the reorder operation — naming\neither is a 422. A backlight must be renderable: a `single` backlight\nneeds a `hexcode`, a `multi` or `flow` backlight needs\n`multi_colour_hexcodes` (422 `colour_value_required`), and the\n`custom_texture` finish is refused with a 422 `texture_image_required`\nuntil image upload ships. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createBacklight","parameters":[{"in":"header","name":"Idempotency-Key","example":"2f4e6a8c-0b1d-4e3f-9a5b-7c8d9e0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backlight create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateBacklight","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder backlights\n\nSets the storefront presentation order of the customiser's backlights.\n`backlight_ids` must be the complete current membership of the family,\neach id exactly once, in the order to present them; a partial or stale\nlist is refused with a 422 naming the missing, unknown, and duplicated\nids rather than being partially applied. Competing reorders of unchanged\nmembership are last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderBacklights","parameters":[{"in":"header","name":"Idempotency-Key","example":"5c7e9f1b-3d4a-4c8d-9e2f-4a6b8c0d2e3f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backlight reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderBacklights","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id, from the list backlights operation.","example":"14","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a backlight\n\nReturns one backlight of the customiser. Requires `customisers:read` and\nis safe to retry. A backlight belonging to another customiser or another\nstore is indistinguishable from an unknown id, and a colour of the main\ncolour selection is never reachable here.","operationId":"getBacklight","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a backlight\n\nApplies an RFC 7396 JSON Merge Patch to one backlight: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`, `hexcode`, `multi_colour_hexcodes`, `min_width_cm`,\n`min_height_cm`). Arrays are replaced wholesale. Read-only and unknown\nfields are rejected — sending one at all is a 422, whatever its value. An\nempty object `{}` is a valid no-op. The patched result must still be\nrenderable: clearing the last hex value of a `single` backlight, or\nswitching to `multi`/`flow` without hex values, is a 422\n`colour_value_required`, and switching to the `custom_texture` finish is\nrefused with a 422 `texture_image_required` unless the backlight already\ncarries a texture image. Requires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateBacklight","parameters":[{"in":"header","name":"Idempotency-Key","example":"3a5c7e9b-1d2f-4a6b-8c0d-2e4f6a8b0c1d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightRequest2"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a backlight\n\nRemoves the backlight and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA backlight anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — references are never\nsilently cleared. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteBacklight","parameters":[{"in":"header","name":"Idempotency-Key","example":"4b6d8f0a-2c3e-4b7c-9d1e-3f5a7b9c1d2e","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["backlight_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Backlight still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id.","example":"14","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backlight update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateBacklight","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id.","example":"14","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a backlight delete\n\nDry-runs the delete without removing anything: an in-use backlight\nreturns the identical 409 `resource_in_use` problem the delete would\nreturn, and a deletable one returns a 200 verdict. Nothing is written and\nno idempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteBacklight","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Backlight still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the backlight's images to write (closed set): the swatch a shopper picks from, the example photograph beside it, or the texture the custom_texture finish renders from.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a backlight image\n\nTakes the raster image of a staged upload and puts it in one of the\nbacklight's three image slots, returning the backlight with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for `image` and `example_image`, `option_texture_image`\nfor `texture_image` — send the bytes to the presigned target it returns,\nthen send that upload's id here. An upload whose purpose is not the\nslot's answers 422 `upload_purpose_mismatch`. The upload is single use:\nattaching it a second time answers 409, and each slot needs its own\nupload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for\nit stops resolving.\n\nAttaching to `texture_image` is what makes the `custom_texture` finish\navailable: send the image here first, then set `texture` on the\nbacklight itself. The attach clears the customiser's cached storefront\nconfiguration and runs the advisory language sync. It never regenerates\nShopify products, product images, or the product cache.","operationId":"attachBacklightImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightImageResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a backlight image\n\nClears one of the backlight's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the backlight as it\nnow stands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nDeleting the texture image of a backlight whose `texture` is\n`custom_texture` is refused with 422 `texture_image_required`: the finish\nrenders from that image, and the storefront could not draw the backlight\nwithout it. Change `texture` on the backlight first, then delete the\nimage.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteBacklightImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BacklightImageSlot"},"customiser_id":{"type":"integer"},"backlight_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a backlight image attach\n\nDry-runs the attach: the slot must exist on this backlight, the upload must\nexist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachBacklightImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown backlight, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/backlights/{backlight_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"backlight_id","description":"The backlight id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a backlight image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, a texture image the `custom_texture` finish still needs answers\nthe identical 422, and anything else returns a 200 verdict. Nothing is\nwritten, nothing is removed, and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteBacklightImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser backlights"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List colours\n\nLists the customiser's colour options in the order the storefront\npresents them (`sort_order`, then id). `base_price` is integer minor\nunits of `currency`; minimum sizes are centimetres; image fields are\nread-only public URLs. Requires `customisers:read`. Follow `links.next`\nwhile `pagination.has_more` is true, sending each cursor back unchanged.\nThe operation is read-only and always safe to retry.","operationId":"listColours","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a colour\n\nAdds a colour to the customiser's main colour selection, at the end of\nthe current presentation order. Only `name` is required; every other\nfield has a documented default (`colour_type` single, `texture` flat,\n`min_size_rule` any, `base_price` 0, `price_multiplier` 1,\n`price_multiplier_target` base_letter_price). Image fields are read-only\nuntil the upload surface ships, and `sort_order` moves only through the\nreorder operation — naming either is a 422. A colour must be renderable:\na `single` colour needs a `hexcode`, a `multi` or `flow` colour needs\n`multi_colour_hexcodes` (422 `colour_value_required`), and the\n`custom_texture` finish is refused with a 422 `texture_image_required`\nuntil image upload ships. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"colour_type":{"$ref":"#/components/schemas/CustomiserBacklightColourType"},"texture":{"$ref":"#/components/schemas/CustomiserBacklightTexture"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The colour's display name.","example":"Warm White"},"description":{"$ref":"#/components/schemas/NullableString"},"hexcode":{"$ref":"#/components/schemas/NullableString"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/CustomiserColourMultiColourHexcodes"},"min_width_cm":{"$ref":"#/components/schemas/NullableNumber"},"min_height_cm":{"$ref":"#/components/schemas/NullableNumber"},"base_price":{"type":"integer","description":"A flat amount added when the colour is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the colour is chosen.","example":1.2}},"required":["name"],"additionalProperties":false}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a colour create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder colours\n\nSets the storefront presentation order of the customiser's colours.\n`colour_ids` must be the complete current membership of the family,\neach id exactly once, in the order to present them; a partial or stale\nlist is refused with a 422 naming the missing, unknown, and duplicated\nids rather than being partially applied. Competing reorders of unchanged\nmembership are last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderColours","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a colour reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderColours","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id, from the list colours operation.","example":"7","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a colour\n\nReturns one colour of the customiser's main colour selection. Requires\n`customisers:read` and is safe to retry. A colour belonging to another\ncustomiser, another store, or a letter part is indistinguishable from an\nunknown id.","operationId":"getColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a colour\n\nApplies an RFC 7396 JSON Merge Patch to one colour: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`, `hexcode`, `multi_colour_hexcodes`, `min_width_cm`,\n`min_height_cm`). Arrays are replaced wholesale. Read-only and unknown\nfields are rejected — sending one at all is a 422, whatever its value. An\nempty object `{}` is a valid no-op. The patched result must still be\nrenderable: clearing the last hex value of a `single` colour, or switching\nto `multi`/`flow` without hex values, is a 422 `colour_value_required`,\nand switching to the `custom_texture` finish is refused with a 422\n`texture_image_required` unless the colour already carries a texture\nimage. Requires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"colour_type":{"$ref":"#/components/schemas/CustomiserBacklightColourType"},"texture":{"$ref":"#/components/schemas/CustomiserBacklightTexture"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The colour's display name.","example":"Warm White"},"description":{"$ref":"#/components/schemas/NullableString"},"hexcode":{"$ref":"#/components/schemas/NullableString"},"multi_colour_hexcodes":{"$ref":"#/components/schemas/CustomiserColourMultiColourHexcodes"},"min_width_cm":{"$ref":"#/components/schemas/NullableNumber"},"min_height_cm":{"$ref":"#/components/schemas/NullableNumber"},"base_price":{"type":"integer","description":"A flat amount added when the colour is chosen, in integer minor units.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the colour is chosen.","example":1.2}},"additionalProperties":false}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a colour\n\nRemoves the colour and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A\ncolour anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — references are never\nsilently cleared. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["colour_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a colour update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a colour delete\n\nDry-runs the delete without removing anything: an in-use colour returns\nthe identical 409 `resource_in_use` problem the delete would return, and\na deletable one returns a 200 verdict. Nothing is written and no\nidempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the colour's images to write (closed set): the swatch a shopper picks from, the example photograph beside it, or the texture the custom_texture finish renders from.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a colour image\n\nTakes the raster image of a staged upload and puts it in one of the\ncolour's three image slots, returning the colour with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for `image` and `example_image`, `option_texture_image`\nfor `texture_image` — send the bytes to the presigned target it returns,\nthen send that upload's id here. An upload whose purpose is not the\nslot's answers 422 `upload_purpose_mismatch`. The upload is single use:\nattaching it a second time answers 409, and each slot needs its own\nupload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for\nit stops resolving.\n\nAttaching to `texture_image` is what makes the `custom_texture` finish\navailable: send the image here first, then set `texture` on the colour\nitself. The attach clears the customiser's cached storefront\nconfiguration and runs the advisory language sync. It never regenerates\nShopify products, product images, or the product cache.","operationId":"attachColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightImageResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a colour image\n\nClears one of the colour's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the colour as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nDeleting the texture image of a colour whose `texture` is\n`custom_texture` is refused with 422 `texture_image_required`: the finish\nrenders from that image, and the storefront could not draw the colour\nwithout it. Change `texture` on the colour first, then delete the image.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BacklightImageSlot"},"customiser_id":{"type":"integer"},"colour_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a colour image attach\n\nDry-runs the attach: the slot must exist on this colour, the upload must\nexist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse3"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/colours/{colour_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a colour image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, a texture image the `custom_texture` finish still needs answers\nthe identical 422, and anything else returns a 200 verdict. Nothing is\nwritten, nothing is removed, and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse3"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List extras\n\nLists the customiser's extras in the order the storefront presents them\n(`sort_order`, then id). `input_type` decides which fields the extra\nreads; the ones it never reads come back as null. `base_price` and each\nchoice's `base_price` are integer minor units of `currency`. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.","operationId":"listExtras","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserExtraItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create an extra\n\nAdds an extra to the customiser, at the end of the current presentation\norder. `name` and `input_type` are required; every other field has a\ndocumented default (`default_value` false, both exclusion lists empty,\n`base_price` 0, `price_multiplier` 0, `price_multiplier_target`\nbase_letter_price).\n\nThe input type decides which fields the extra reads, and naming one it\nnever reads is a 422 `field_not_used_by_input_type` on that field. A\n`yes_no` extra reads its labels, default answer, colour exclusions, and\nits own price; a `dropdown` or `image_choice` reads `select_options`,\nwhere each choice carries its own price; a `notes` extra reads\n`char_limit`. A dropdown or image choice needs at least one choice.\n\nAn extra charges a flat amount or a multiplier, never both: naming one\nclears the other, and a body naming both is a 422 `price_mode_conflict`.\nA defaulted price charges nothing.\n\nColour exclusions name the customiser's own colours — an id from another\ncustomiser or another store is a 422 on the offending element — and each\none may be scoped to a single `letter_part` or left unscoped with null.\nChoice images cannot be set here: image upload is a follow-up surface, so\n`image_url` and `example_image_url` are read-only and an image-choice\nextra created through the API draws its choices without pictures until one\nis uploaded in the merchant admin.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createExtra","parameters":[{"in":"header","name":"Idempotency-Key","example":"d2c1b0a9-8f7e-4d6c-9b5a-4e3d2c1b0a99","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserExtraData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Field the input type never reads","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input_type":{"type":"string","description":"How the extra asks the shopper for its answer. It decides which of the other fields the extra reads.","example":"yes_no","enum":["yes_no","dropdown","image_choice","notes"]},"select_options":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"},"yes_colour_exclusions":{"type":"array","description":"The colours a yes answer takes away, as {colour_id, letter_part} objects. letter_part is null for an exclusion covering the whole letter.","example":[[]],"items":{"type":"object","properties":{"letter_part":{"$ref":"#/components/schemas/CustomiserExtraYesColourExclusionLetterPart"}}}},"no_colour_exclusions":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The extra's display name.","example":"Dimmer"},"description":{"type":"string","description":"A description shown alongside the option.","example":"Adds a dimmer switch to the transformer"},"true_label":{"type":"string","description":"What a yes_no extra calls its yes answer.","example":"Yes"},"false_label":{"type":"string","description":"What a yes_no extra calls its no answer.","example":"No"},"default_value":{"type":"boolean","description":"The answer a yes_no extra starts on.","example":false},"char_limit":{"type":"integer","description":"The longest answer a notes extra accepts. Only a notes extra reads it, so the worked example — a yes/no extra — leaves it out.","example":null},"base_price":{"type":"integer","description":"A flat amount added when a yes_no extra is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when a yes_no extra is chosen, instead of base_price rather than alongside it.","example":null}},"required":["input_type","name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an extra create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateExtra","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder extras\n\nSets the storefront presentation order of the customiser's extras.\n`extra_ids` must be the complete current membership of the family, each id\nexactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderExtras","parameters":[{"in":"header","name":"Idempotency-Key","example":"e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserExtraData"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an extra reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderExtras","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id, from the list extras operation.","example":"9","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get an extra\n\nReturns one of the customiser's extras. Requires `customisers:read` and is\nsafe to retry. An extra belonging to another customiser or another store\nis indistinguishable from an unknown id.","operationId":"getExtra","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserExtraItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update an extra\n\nApplies an RFC 7396 JSON Merge Patch to one extra: properties omitted from\nthe body are unchanged, and `null` clears a nullable property. Arrays are\nreplaced wholesale, so sending `select_options` replaces the whole list of\nchoices and sending an exclusion list replaces the whole list. Read-only\nand unknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\nA choice keeps the images uploaded against it as long as its `value` stays\nthe same, because image upload is a follow-up surface and a rename must\nnot destroy a picture the API cannot restore. A choice with a new value\nstarts without images.\n\n`input_type` may be changed. The fields the new type does not read are\nemptied in the same write, because the read projection publishes them as\nnull and a row that disagreed with its projection would be unreachable\nfrom either surface. `select_options` is the exception: it keeps its\nchoices and their uploaded images, published as null while the input type\ndoes not read them, so moving an extra to `yes_no` or `notes` and back to\n`dropdown` or `image_choice` returns the same choices. Replacing that list\ntakes an explicit `select_options` write.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateExtra","parameters":[{"in":"header","name":"Idempotency-Key","example":"7a6b5c4d-3e2f-4a1b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"input_type":{"type":"string"},"true_label":{"type":"string"},"false_label":{"type":"string"},"default_value":{"type":"boolean"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"type":"array","items":{"type":"object","properties":{"colour_id":{"type":"integer"},"letter_part":{"type":"string"}}}},"no_colour_exclusions":{"type":"array"},"select_options":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The extra's display name.","example":"Dimmer"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"Adds a dimmer switch"},"true_label":{"type":"string","description":"What a yes_no extra calls its yes answer.","example":"Yes please"},"default_value":{"type":"boolean","description":"The answer a yes_no extra starts on.","example":true},"char_limit":{"type":"integer","description":"The longest answer a notes extra accepts; null removes the cap. Only a notes extra reads it, so the worked example leaves it out.","example":null},"base_price":{"type":"integer","description":"A flat amount added when a yes_no extra is chosen, in integer minor units.","example":500}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete an extra\n\nRemoves the extra and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. An extra\nanything still references is refused with a 409 `resource_in_use` listing\nthe referencing rows — saved presets that answered it — and references are\nnever silently cleared. Requires `customisers:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteExtra","parameters":[{"in":"header","name":"Idempotency-Key","example":"b3c4d5e6-f7a8-4b9c-8d0e-1f2a3b4c5d6e","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["extra_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserExtraData"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Extra still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an extra update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateExtra","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an extra delete\n\nDry-runs the delete without removing anything: an in-use extra returns the\nidentical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteExtra","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtraValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Extra still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the extra's images to write (closed set): the picture shown for the add-on itself.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach an extra image\n\nTakes the raster image of a staged upload and puts it in the extra's\nimage slot, returning the extra with that slot's `*_url` pointing at the\nnew file. Requires `customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachExtraImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"input_type":{"type":"string"},"true_label":{"type":"string"},"false_label":{"type":"string"},"default_value":{"type":"boolean"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"type":"array"},"no_colour_exclusions":{"type":"array"},"select_options":{"$ref":"#/components/schemas/NullableString"},"image_url":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete an extra image\n\nClears one of the extra's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the extra as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteExtraImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/ExtraImageSlot"},"customiser_id":{"type":"integer"},"extra_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserExtraItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate an extra image attach\n\nDry-runs the attach: the slot must exist on this extra, the upload must\nexist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachExtraImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse4"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate an extra image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteExtraImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse4"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/choices/{choice_value}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"choice_value","description":"The choice's value, percent-encoded as one path segment. The empty string, and any value containing a forward slash, cannot be addressed.","example":"oak","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which of the choice's pictures to write (closed set): the artwork the choice is drawn as, or the example photograph shown beside it.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach an extra choice image\n\nTakes the raster image of a staged upload and puts it on one choice of a\ndropdown or image-choice extra, returning the extra with that choice's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe choice is addressed by its `value`, percent-encoded as a single path\nsegment (RFC 3986): a choice valued `warm white` is\n`…/choices/warm%20white/images/image`. A value the extra does not offer\nanswers 404, exactly as an unknown extra id does. Two values cannot be\naddressed at all and have to be renamed first: the empty string, and any\nvalue containing `/`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for both slots — send the bytes to the presigned target it\nreturns, then send that upload's id here. An upload whose purpose is not\nthe slot's answers 422 `upload_purpose_mismatch`. The upload is single\nuse: attaching it a second time answers 409, and each slot needs its own\nupload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for it\nstops resolving.\n\nA picture survives a later `select_options` rewrite that keeps the\nchoice's `value`, because a write matches each submitted choice to the\nstored one with the same value and carries the images across. A choice\ngiven a new value starts without pictures. An attach racing such a rewrite\ncannot be silently dropped: both writers compose the stored list under the\nextra's row lock, so whichever commits second is composed from the other's\nresult.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachExtraChoiceImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"7a1b2c3d-4e5f-4061-9a2b-3c4d5e6f7081","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"input_type":{"type":"string"},"true_label":{"$ref":"#/components/schemas/NullableString"},"false_label":{"$ref":"#/components/schemas/NullableString"},"default_value":{"$ref":"#/components/schemas/NullableString"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"$ref":"#/components/schemas/NullableString"},"no_colour_exclusions":{"$ref":"#/components/schemas/NullableString"},"select_options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"hexcode":{"$ref":"#/components/schemas/NullableString"},"image_url":{"type":"string"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"type":"integer"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"}}}},"image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/NullableString"},"currency":{"type":"string"},"price_multiplier":{"$ref":"#/components/schemas/NullableString"},"price_multiplier_target":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra, choice, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete an extra choice image\n\nClears one of a choice's two picture slots and deletes the stored file,\nreturning a receipt naming what was removed and the extra as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nNothing is refused: a choice with no picture is the state every extra\ncreated over this API starts in, and the customer customiser draws such a\nchoice as its label.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteExtraChoiceImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"5e6f7081-9a2b-4c3d-8e4f-5061a2b3c4d5","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string","description":"The picture slot that was cleared.","enum":["image","example_image"]},"customiser_id":{"type":"integer"},"extra_id":{"type":"integer"},"choice_value":{"type":"string","description":"The value of the choice the picture was removed from."},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"input_type":{"type":"string"},"true_label":{"$ref":"#/components/schemas/NullableString"},"false_label":{"$ref":"#/components/schemas/NullableString"},"default_value":{"$ref":"#/components/schemas/NullableString"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"yes_colour_exclusions":{"$ref":"#/components/schemas/NullableString"},"no_colour_exclusions":{"$ref":"#/components/schemas/NullableString"},"select_options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"hexcode":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"type":"integer"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"}}}},"image_url":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/NullableString"},"currency":{"type":"string"},"price_multiplier":{"$ref":"#/components/schemas/NullableString"},"price_multiplier_target":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"description":"The extra as it now stands, with the choice's slot cleared. Unlike a row deletion receipt, this describes the state after the delete, because both the extra and the choice survive it."}},"required":["object","slot","choice_value","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/choices/{choice_value}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"choice_value","description":"The choice's value, percent-encoded as one path segment.","example":"oak","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which picture to write: `image` or `example_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate an extra choice image attach\n\nDry-runs the attach: the extra must offer this choice, the slot must be\none of the two, the upload must exist for this store and this API key, and\nits purpose must be the slot's. Nothing is written, the upload is not\nconsumed, and no idempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachExtraChoiceImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse5"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown extra, choice, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/extras/{extra_id}/choices/{choice_value}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"extra_id","description":"The extra id.","example":"9","required":true,"schema":{"type":"integer"}},{"in":"path","name":"choice_value","description":"The choice's value, percent-encoded as one path segment.","example":"oak","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which picture to remove: `image` or `example_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate an extra choice image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteExtraChoiceImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse5"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser extras"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List fonts\n\nLists the customiser's font options in the order the storefront presents\nthem (`sort_order`, then id). Minimum letter heights are decimal\ncentimetres. `font_file` and every field derived from the uploaded\ntypeface are read-only. Requires `customisers:read`. Follow `links.next`\nwhile `pagination.has_more` is true, sending each cursor back unchanged.\nThe operation is read-only and always safe to retry.","operationId":"listFonts","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFontItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a font\n\nAdds a font to the customiser, at the end of the current presentation\norder. Only `name` is required; every other field has a documented\ndefault (`line_height` null, `stencil_safe` false, every reference null).\n\nThe created font has no typeface. Font-file uploads belong to the\ndeferred file-upload surface, so this operation produces a row the\nstorefront cannot render — `font_file.has_font_file` comes back false.\nUpload the typeface in the merchant admin to finish the font. Until that\nsurface ships, the useful operations here are updating and reordering the\nfonts a customiser already has.\n\nA font is priced by the reference its customiser's pricing model needs: a\n`font_tier_id` on a customiser priced per sign size, a\n`length_pricing_id` on one priced by material length. Creating a font\nwithout it is refused with a 422, because the storefront cannot price the\nfont. Every reference must name one of this customiser's own rows.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createFont","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFontData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Font pricing required","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The font's display name.","example":"Neon Script"},"line_height":{"type":"number","description":"The multiple of the letter height each line of text is spaced by. Omit or send null to use the typeface's own line height.","example":1.2},"stencil_safe":{"type":"boolean","description":"Whether the font's letters hold together when cut as a stencil.","example":false},"min_height_smallest_letter_cm":{"type":"number","description":"The smallest height, in centimetres, the font's shortest letter may be produced at.","example":4.5},"min_height_uppercase_letter_cm":{"type":"number","description":"The smallest height, in centimetres, an uppercase letter may be produced at.","example":6},"font_tier_id":{"type":"integer","description":"The customiser's font tier this font is priced by.","example":7},"length_pricing_id":{"type":"integer","description":"The customiser's length pricing this font is priced by.","example":4},"minimum_size_id":{"type":"integer","description":"The customiser's smallest size this font is offered from.","example":3},"minimum_legacy_fixed_height_size_id":{"type":"integer","description":"The customiser's smallest legacy fixed-height size this font is offered from.","example":5}},"required":["name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateFont","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder fonts\n\nSets the storefront presentation order of the customiser's fonts.\n`font_ids` must be the complete current membership of the family, each id\nexactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderFonts","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FontReorderItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderFonts","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id, from the list fonts operation.","example":"11","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a font\n\nReturns one of the customiser's fonts. Requires `customisers:read` and is\nsafe to retry. A font belonging to another customiser or another store is\nindistinguishable from an unknown id.","operationId":"getFont","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFontResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a font\n\nApplies an RFC 7396 JSON Merge Patch to one font: properties omitted from\nthe body are unchanged, and `null` clears a nullable property\n(`line_height`, the minimum letter heights, and every reference). Setting\n`line_height` to null returns the font to its typeface's own line height,\nwhich `uses_default_line_height` then reports as true. Read-only and\nunknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\nA patch that would leave the font without the pricing reference its\ncustomiser's pricing model needs is refused with a 422, and every\nreference must name one of this customiser's own rows.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateFont","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFontResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The font's display name.","example":"Neon Script"},"line_height":{"type":"number","description":"The multiple of the letter height each line of text is spaced by; null returns the font to the typeface's own line height.","example":1.2},"stencil_safe":{"type":"boolean","description":"Whether the font's letters hold together when cut as a stencil.","example":false},"min_height_smallest_letter_cm":{"type":"number","description":"The smallest height, in centimetres, the font's shortest letter may be produced at; null clears it.","example":4.5},"min_height_uppercase_letter_cm":{"type":"number","description":"The smallest height, in centimetres, an uppercase letter may be produced at; null clears it.","example":6},"font_tier_id":{"type":"integer","description":"The customiser's font tier this font is priced by; null clears it.","example":7},"length_pricing_id":{"type":"integer","description":"The customiser's length pricing this font is priced by; null clears it.","example":4},"minimum_size_id":{"type":"integer","description":"The customiser's smallest size this font is offered from; null clears it.","example":3},"minimum_legacy_fixed_height_size_id":{"type":"integer","description":"The customiser's smallest legacy fixed-height size this font is offered from; null clears it.","example":5}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a font\n\nRemoves the font and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A font\nanything still references is refused with a 409 `resource_in_use` listing\nthe referencing rows — the saved presets that chose it — and references\nare never silently cleared. Requires `customisers:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteFont","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["font_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/FontReorderItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Font still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateFont","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font delete\n\nDry-runs the delete without removing anything: an in-use font returns the\nidentical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no\nidempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteFont","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Font still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}/file":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id.","example":"11","required":true,"schema":{"type":"integer"}}],"put":{"summary":"Attach a font file\n\nTakes the TrueType file of a staged upload and makes it this font's\ntypeface, returning the font with `font_file.has_font_file` true and a\nlive `ttf_url`. Requires `customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` using purpose\n`font_ttf`, send the bytes to the presigned target it returns, then send\nthat upload's id here. The upload is single use: attaching it a second\ntime answers 409, and a replacement typeface needs a new upload.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with TrueType magic, and carry a table\ndirectory a renderer can read. A file that fails leaves the upload\n`rejected` with the failure's code, and attaching it again replays that\ncode — so fix the file and create a new upload rather than retrying.\n\nSending this to a font that already has a typeface replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach mints a fresh server-owned `font_file.family_name`, clears the\ncustomiser's cached storefront configuration, and runs the advisory\nlanguage sync. It never regenerates Shopify products, product images, or\nthe product cache.","operationId":"attachFontFile","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"line_height":{"$ref":"#/components/schemas/NullableString"},"uses_default_line_height":{"type":"boolean"},"stencil_safe":{"type":"boolean"},"min_height_smallest_letter_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_uppercase_letter_cm":{"$ref":"#/components/schemas/NullableString"},"font_tier_id":{"type":"integer"},"length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"minimum_size_id":{"$ref":"#/components/schemas/NullableString"},"minimum_legacy_fixed_height_size_id":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"font_file":{"type":"object","properties":{"has_font_file":{"type":"boolean"},"family_name":{"type":"string","description":"A fresh server-owned CSS family token, minted by this attach. It is never parsed from the typeface, and a replacement mints a new one."},"ttf_url":{"type":"string"}},"required":["family_name"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Not a TrueType file","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a font file\n\nRemoves this font's typeface and deletes the stored file, returning a\nreceipt naming what was removed and the font as it now stands. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nThis stops the font rendering. A file-less font is a state the\nmerchant admin allows and the storefront understands, but a shopper who\nselects a font whose file cannot be loaded gets a sign that will not add\nto cart. `font_file.has_font_file` comes back false, which is the signal\nto either upload a replacement or stop offering the font. Deleting a font\nthat has no file answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteFontFile","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string"},"customiser_id":{"type":"integer"},"font_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserFontData"}},"required":["object","removed_file","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No file in the slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}/file/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font file attach\n\nDry-runs the attach: the upload must exist for this store and this API\nkey, and its purpose must be `font_ttf`. Nothing is written, the upload is\nnot consumed, and no idempotency key is spent. Requires\n`customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable TrueType — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is attachable\",\nnot \"the file is sound\".","operationId":"validateAttachFontFile","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/fonts/{font_id}/file/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_id","description":"The font id.","example":"11","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font file delete\n\nDry-runs the delete: a font with no typeface answers the identical 404 the\ndelete would, and one with a file returns a 200 verdict. Nothing is\nwritten, nothing is removed, and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.\n\nA valid verdict is not an endorsement: deleting a font's file is always\nallowed and always stops the font rendering. See the delete operation.","operationId":"validateDeleteFontFile","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No file in the slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser fonts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/font-tiers":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List font tiers\n\nLists the customiser's font tiers, oldest first. Each tier carries one\nrow per size, and each row prices the sign by line count: `base_price`\nand `letter_price` are maps of `line_1` … `line_10` in integer minor\nunits of `currency`. These are the same price lists the customiser\npricing document publishes under `price_lists`. Requires `pricing:read`.\nFollow `links.next` while `pagination.has_more` is true, sending each\ncursor back unchanged. The operation is read-only and always safe to\nretry.","operationId":"listFontTiers","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFontTierItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"security":[{"default":["pricing:read"]}]},"post":{"summary":"Create a font tier\n\nAdds a font tier to the customiser. Only `name` is required; every other\nfield has a documented default (`letter_pricing_mode` every_letter_cost,\n`rows` empty). Each row's `size_id` must name one of the customiser's own\nsizes, and carries a `base_price` and a `letter_price` map keyed `line_1`\n… `line_10` in integer minor units. `line_1` to `line_3` are required in\nboth maps, because the pricing document requires them of these same rows;\na higher line the map omits is stored as no price for that line count.\n\nThe customiser must price by `simple_letter` (or the legacy\n`legacy_fixed_height` model): a font tier on any other pricing model is\nunreachable, and the write is refused with a 422 `pricing_model_mismatch`.\n\nRequires `pricing:write` and an `Idempotency-Key`. The pricing document\npublishes the same tiers as its `price_lists` and its full replace\ndeletes any tier the payload omits, so pick one surface per workflow\nrather than interleaving the two.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createFontTier","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFontTierResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Wrong pricing model","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letter_pricing_mode":{"$ref":"#/components/schemas/CustomiserFontTierLetterPricingMode"},"name":{"type":"string","description":"The tier's display name.","example":"Standard"},"rows":{"type":"array","description":"One priced row per size.","example":[{"size_id":3,"base_price":{"line_1":2500,"line_2":3000,"line_3":3500},"letter_price":{"line_1":400,"line_2":450,"line_3":500}}],"items":{"type":"object","properties":{"size_id":{"type":"integer","description":"One of the customiser's own size ids.","example":3},"base_price":{"$ref":"#/components/schemas/CustomiserProductCart"},"letter_price":{"$ref":"#/components/schemas/CustomiserProductCart"},"letter_price_by_position":{"$ref":"#/components/schemas/CustomiserProductCart"}},"required":["size_id","base_price","letter_price"]}}},"required":["name"]}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/font-tiers/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font tier create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `pricing:write`.","operationId":"validateCreateFontTier","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"font_tier_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/font-tiers/{font_tier_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_tier_id","description":"The font tier id, from the list font tiers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a font tier\n\nReturns one of the customiser's font tiers. Requires `pricing:read` and\nis safe to retry. A font tier belonging to another customiser or another\nstore is indistinguishable from an unknown id.","operationId":"getFontTier","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFontTierItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font tier","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"security":[{"default":["pricing:read"]}]},"patch":{"summary":"Update a font tier\n\nApplies an RFC 7396 JSON Merge Patch to one font tier: properties omitted\nfrom the body are unchanged. `rows` is an array, so sending it replaces\nevery row wholesale. Read-only and unknown fields are rejected. An empty\nobject `{}` is a valid no-op. Requires `pricing:write`.\n\nOne exception to wholesale replacement protects per-position pricing: a\nrow resent without `letter_price_by_position` keeps the schedule its size\nalready had, because dropping a schedule by forgetting to echo it would\nsilently reprice a sign. Send `letter_price_by_position: null` to clear\none deliberately.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateFontTier","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFontTierResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font tier","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The tier's display name.","example":"Standard"},"rows":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"}}}}}},"security":[{"default":["pricing:write"]}]},"delete":{"summary":"Delete a font tier\n\nRemoves the font tier and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA tier anything still references is refused with a 409 `resource_in_use`\nlisting the referencing rows — the fonts priced by it, and the customiser\nsettings that price icons by it — and references are never silently\ncleared. Requires `pricing:write` and an `Idempotency-Key`; retrying the\nsame request and key replays the original receipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteFontTier","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["font_tier_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"currency":{"type":"string"},"rows":{"type":"array"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"description":"The font tier exactly as it was immediately before deletion."}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font tier","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Font tier still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/font-tiers/{font_tier_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_tier_id","description":"The font tier id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font tier update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `pricing:write`.","operationId":"validateUpdateFontTier","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontTierValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font tier","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/font-tiers/{font_tier_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"font_tier_id","description":"The font tier id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a font tier delete\n\nDry-runs the delete without removing anything: an in-use tier returns the\nidentical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no\nidempotency key is consumed. The verdict is advisory. Requires\n`pricing:write`.","operationId":"validateDeleteFontTier","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FontTierValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown font tier","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Font tier still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser font tiers"],"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/forms":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List forms\n\nLists the customiser's forms in the order the storefront presents them\n(`sort_order`, then id). `recipient_emails` is the receiver list the\nsubmission is mailed to, and `fields` is the form's field document.\nRequires `customisers:read`. Follow `links.next` while\n`pagination.has_more` is true, sending each cursor back unchanged. The\noperation is read-only and always safe to retry.","operationId":"listForms","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFormItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a form\n\nAdds a form to the customiser, at the end of the current presentation\norder. `name` and `form_type` are required; every other property has a\ndocumented default (no receivers, `save_customer_details` false,\n`post_submission_action` default, and no fields).\n\n`fields` is the form's field document. Each field names the input it\nrenders as, and only the properties that input reads: a `min` on a text\nfield, or `options` on anything but a dropdown or multiple-choice field,\nis a 422 `field_not_used_by_input_type` on the offending property.\nIdentities (`field_id`, `option_id`) are generated when omitted; send\nthem back unchanged to keep a field's existing submissions attached to it.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync, which is what translates the form's copy into\nthe customiser's other languages. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createForm","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserFormData"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Property the input type does not read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"form_type":{"$ref":"#/components/schemas/CustomiserFormFormType"},"post_submission_action":{"type":"string","description":"What the storefront does once the form is submitted. Defaults to default.","example":"default","enum":["default","redirect"]},"fields":{"type":"array","description":"The form's fields, in the order the shopper answers them. Replaces the stored document wholesale; an empty list clears it. Defaults to no fields. Each entry carries only the properties its own input_type reads — the example below is one email field.","example":[{"name":"Email","input_type":"email","customer_field":"email","required":true,"reply_to":true}],"items":{"type":"object","properties":{"input_type":{"$ref":"#/components/schemas/CustomiserFormFieldInputType"},"options":{"type":"array","description":"The choices the field offers. Dropdown and multiple-choice fields only, and they must offer at least one.","example":[[]],"items":{"type":"object","properties":{"label":{"type":"string","description":"What the shopper reads for this choice.","example":null},"value":{"type":"string","description":"What the submission carries for this choice.","example":null},"hidden":{"type":"boolean","description":"Whether the choice is hidden from the shopper.","example":null},"option_id":{"type":"string","description":"The choice's identity. Generated when omitted.","example":null}},"required":["label","value"]}},"customer_field":{"type":"string","description":"The shopper detail this field carries. Defaults to none.","example":"email","enum":["none","first_name","last_name","email","phone"]},"name":{"type":"string","description":"The field's label.","example":"Email"},"required":{"type":"boolean","description":"Whether the shopper must answer.","example":true},"field_id":{"type":"string","description":"The field's identity. Generated when omitted.","example":"3f2a9c1e-5d6b-4a7c-8e9f-0a1b2c3d4e5f"},"info":{"type":"string","description":"Help text shown under the field.","example":"We only use this to send your quote"},"placeholder":{"type":"string","description":"Placeholder text.","example":"you@example.com"},"checkbox_value":{"type":"string","description":"What a ticked checkbox submits. Checkbox fields only.","example":"yes"},"min":{"type":"integer","description":"The lowest accepted number. Number fields only.","example":1},"max":{"type":"integer","description":"The highest accepted number. Number fields only.","example":10},"min_date":{"type":"string","description":"The earliest accepted date, as YYYY-MM-DD. Date fields only.","example":"2026-01-01"},"max_date":{"type":"string","description":"The latest accepted date, as YYYY-MM-DD. Date fields only.","example":"2026-12-31"},"allow_multiple":{"type":"boolean","description":"Whether several files may be attached. File fields only.","example":true},"reply_to":{"type":"boolean","description":"Whether the address answered here becomes the reply-to of the submission email. Email fields only.","example":true}},"required":["input_type","name"]}},"name":{"type":"string","description":"The form's display name.","example":"Request a quote"},"description":{"type":"string","description":"Copy shown above the form.","example":"Tell us about your project"},"button_label":{"type":"string","description":"The submit button's label.","example":"Send"},"subject":{"type":"string","description":"The subject line of the email the submission is sent as.","example":"New quote request"},"recipient_emails":{"$ref":"#/components/schemas/OrderProducts"},"save_customer_details":{"type":"boolean","description":"Whether answers mapped to a customer_field are saved to the shopper's customer record.","example":true},"redirect_url":{"type":"string","description":"Where to send the shopper after submission. Required while post_submission_action is redirect, and must be an HTTPS URL.","example":"https://example.com/thank-you"}},"required":["form_type","name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a form create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateForm","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder forms\n\nSets the storefront presentation order of the customiser's forms.\n`form_ids` must be the complete current membership of the family, each id\nexactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderForms","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserFormData"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a form reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderForms","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/{form_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"form_id","description":"The form id, from the list forms operation.","example":"7","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a form\n\nReturns one of the customiser's forms. Requires `customisers:read` and is\nsafe to retry. A form belonging to another customiser or another store is\nindistinguishable from an unknown id.","operationId":"getForm","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown form","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a form\n\nApplies an RFC 7396 JSON Merge Patch to one form: properties omitted from\nthe body are unchanged, and `null` clears a nullable property. Arrays\nreplace wholesale, so sending `fields` replaces the whole field document\nand sending `recipient_emails` replaces the whole receiver list. An empty\nobject `{}` is a valid no-op, and an empty `fields` list clears the field\ndocument. The create operation documents the whole field schema; the same\nproperties and the same per-input-type rules apply here. Read-only and\nunknown properties are rejected. Requires `customisers:write`.\n\nChanging `recipient_emails` also clears any suppression the mail\ntransport recorded against the old receivers and resumes delivery, which\nis the same repair the merchant admin performs.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateForm","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown form","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"fields":{"type":"array","description":"The form's fields, replacing the stored document wholesale. An empty list clears it; omitting the property leaves it alone.","example":[[]],"items":{"type":"object","properties":{"name":{"type":"string","description":"The field's label.","example":"Email"},"input_type":{"type":"string","description":"The input the field renders as.","example":"email"}},"required":["name","input_type"]}},"name":{"type":"string","description":"The form's display name.","example":"Request a quote"},"description":{"type":"string","description":"Copy shown above the form; null clears it.","example":"Tell us about your project"},"recipient_emails":{"$ref":"#/components/schemas/OrderProducts"},"redirect_url":{"type":"string","description":"Where to send the shopper after submission; must be an HTTPS URL.","example":"https://example.com/thank-you"}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a form\n\nRemoves the form and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A form\nthe customiser's settings still point at — its quote form or its AI\ndesigner fallback form — is refused with a 409 `resource_in_use` listing\nthe referencing rows, and references are never silently cleared. Past\nsubmissions are historical records and are neither deleted nor counted as\nreferences. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteForm","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["form_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserFormData"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown form","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Form still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse2"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse3"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/{form_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"form_id","description":"The form id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a form update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateForm","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown form","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/FormValidateUpdateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/forms/{form_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"form_id","description":"The form id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a form delete\n\nDry-runs the delete without removing anything: a form the customiser's\nsettings still point at returns the identical 409 `resource_in_use`\nproblem the delete would return, and a deletable one returns a 200\nverdict. Nothing is written and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteForm","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown form","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Form still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse2"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse3"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser forms"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List icons\n\nLists the customiser's icons in the order the storefront presents them\n(`sort_order`, then id). `svg` is the drawing the merchant supplied,\n`rendered_svg` is what the icon converter made of it, and `icon_font`\nreports the generated font this customiser's icons are drawn from.\nRequires `customisers:read`. Follow `links.next` while\n`pagination.has_more` is true, sending each cursor back unchanged. The\noperation is read-only and always safe to retry.","operationId":"listIcons","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserIconResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create an icon\n\nAdds an icon to the customiser, at the end of the current presentation\norder. `name` and `svg` are required.\n\nThe drawing travels as a JSON string rather than a file, so an icon is the\none option a v1 integration can add complete. What happens to it is server\nside and not negotiable: the SVG is sanitised (scripts, event handlers,\nand external references are stripped), converted by the icon service into\nthe shape a font glyph is cut from, and then the font this customiser's\nicons share is rebuilt and each icon's code point stamped back onto its\nrow. `rendered_svg` and `icon_font` report the results and cannot be\nwritten.\n\nA drawing that sanitises to nothing is a 422 on `/svg`. A converter that\ncannot be reached is a 502 `icon_conversion_failed`, and because all of\nthe work happens inside the write's transaction, a failure of either kind\nleaves no icon and no changed font behind.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createIcon","parameters":[{"in":"header","name":"Idempotency-Key","example":"4c5d6e7f-8a9b-4c0d-9e1f-2a3b4c5d6e7f","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserIconResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unsafe SVG","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}},"502":{"description":"Icon converter unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The icon's display name.","example":"Heart"},"svg":{"type":"string","description":"The icon drawing, as an SVG document.","example":"<svg viewBox=\"0 0 24 24\"><path d=\"M4 4h16v16H4z\"/></svg>"},"min_height_cm":{"type":"number","description":"The smallest height in centimetres the icon may be drawn at.","example":5}},"required":["name","svg"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an icon create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. The SVG is sanitised so an unsafe drawing\nis caught here too, but it is not sent to the converter and the icon font\nis not rebuilt — those are the parts that cost, and the create runs them\nitself. Nothing is written, no caches are touched, no idempotency key is\nconsumed, and no option-write budget is spent. The verdict is advisory.\nRequires `customisers:write`.","operationId":"validateCreateIcon","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The icon's display name — the same body the create accepts.","example":"Heart"},"svg":{"type":"string","description":"The icon drawing, as an SVG document.","example":"<svg viewBox=\"0 0 24 24\"><path d=\"M4 4h16v16H4z\"/></svg>"}},"required":["name","svg"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder icons\n\nSets the storefront presentation order of the customiser's icons.\n`icon_ids` must be the complete current membership of the family, each id\nexactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. The generated icon font is untouched — a reorder\nchanges presentation, not glyphs. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderIcons","parameters":[{"in":"header","name":"Idempotency-Key","example":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserIconResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an icon reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderIcons","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/{icon_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"icon_id","description":"The icon id, from the list icons operation.","example":"9","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get an icon\n\nReturns one of the customiser's icons. Requires `customisers:read` and is\nsafe to retry. An icon belonging to another customiser or another store is\nindistinguishable from an unknown id.","operationId":"getIcon","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserIconResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown icon","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update an icon\n\nApplies an RFC 7396 JSON Merge Patch to one icon: properties omitted from\nthe body are unchanged, and `null` clears `min_height_cm`. Read-only and\nunknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\nSending `svg` re-runs the whole server-side pass — sanitise, convert,\nrebuild the font — and replaces `rendered_svg` and the icon's code point.\nRenaming an icon or changing its minimum height does none of that, so it\ncosts nothing and cannot fail on the converter.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateIcon","parameters":[{"in":"header","name":"Idempotency-Key","example":"8d9e0f1a-2b3c-4d5e-9f0a-1b2c3d4e5f6a","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserIconResponse2"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown icon","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}},"502":{"description":"Icon converter unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The icon's display name.","example":"Heart"},"svg":{"type":"string","description":"A replacement drawing, as an SVG document.","example":"<svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"8\"/></svg>"},"min_height_cm":{"type":"number","description":"The smallest height in centimetres the icon may be drawn at; null removes the floor.","example":6}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete an icon\n\nRemoves the icon and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. Requires\n`customisers:write` and an `Idempotency-Key`; retrying the same request\nand key replays the original receipt after the row is gone.\n\nThe generated icon font is left as it is, exactly as the merchant admin\nleaves it: the deleted icon's glyph stays in the font until the next icon\ncreate or update rebuilds it, and nothing draws a glyph no icon points at.\n\nNothing on the server resolves an icon out of a saved design, so no icon\nis ever in use and this delete has no 409 to answer with. The shared\ndelete guard still runs, so the family gains the refusal the moment a\nreference kind is registered for it.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteIcon","parameters":[{"in":"header","name":"Idempotency-Key","example":"0f1a2b3c-4d5e-4f6a-8b9c-0d1e2f3a4b5c","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["icon_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserIconItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown icon","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/{icon_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"icon_id","description":"The icon id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an icon update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed. A\nreplacement drawing is sanitised so an unsafe one is caught here too, but\nit is not converted and the icon font is not rebuilt. Nothing is written,\nno caches are touched, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateUpdateIcon","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown icon","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unsafe SVG","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/icons/{icon_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"icon_id","description":"The icon id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate an icon delete\n\nDry-runs the delete without removing anything, returning a 200 verdict\nwhen the delete would succeed. Nothing on the server resolves an icon out\nof a saved design, so no icon is ever in use and neither this twin nor the\ndelete has a 409 to answer with; both run the same shared guard, so they\ngain that refusal together the moment a reference kind is registered.\nNothing is written and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteIcon","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IconValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown icon","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser icons"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List jackets\n\nLists the customiser's jacket options in the order the storefront presents\nthem (`sort_order`, then id). `base_price` is integer minor units of\n`currency`, and `excluded_colour_ids` names the customiser's own colours.\nRequires `customisers:read`. Follow `links.next` while\n`pagination.has_more` is true, sending each cursor back unchanged. The\noperation is read-only and always safe to retry.","operationId":"listJackets","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserJacketResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a jacket\n\nAdds a jacket to the customiser, at the end of the current presentation\norder. `name` and `jacket_type` are required; every other field has a\ndocumented default (`excluded_colour_ids` empty, `base_price` 0,\n`price_multiplier` 1, `price_multiplier_target` base_letter_price).\n\nA customiser offers each type at most once, so creating a second `white`\nor `coloured` jacket is refused with a 422 on `/jacket_type` naming the\njacket that already holds it. `excluded_colour_ids` must name the\ncustomiser's own colours — an id from another customiser or another store\nis a 422 on the offending element — and `sort_order` moves only through\nthe reorder operation.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createJacket","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserJacketResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Jacket type already offered","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jacket_type":{"type":"string","description":"Whether the jacket keeps the tube's own colour or tints it. Each type may be offered once per customiser.","example":"white","enum":["white","coloured"]},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The jacket's display name.","example":"Clear Jacket"},"description":{"type":"string","description":"A description shown alongside the option.","example":"A clear sleeve that keeps the tube colour"},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the jacket is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the jacket is chosen.","example":1.2}},"required":["jacket_type","name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a jacket create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateJacket","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder jackets\n\nSets the storefront presentation order of the customiser's jackets.\n`jacket_ids` must be the complete current membership of the family, each\nid exactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderJackets","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserJacketResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a jacket reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderJackets","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/{jacket_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"jacket_id","description":"The jacket id, from the list jackets operation.","example":"9","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a jacket\n\nReturns one of the customiser's jackets. Requires `customisers:read` and\nis safe to retry. A jacket belonging to another customiser or another\nstore is indistinguishable from an unknown id.","operationId":"getJacket","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserJacketResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown jacket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a jacket\n\nApplies an RFC 7396 JSON Merge Patch to one jacket: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`). Arrays are replaced wholesale, so sending\n`excluded_colour_ids` replaces the whole exclusion list. Read-only and\nunknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\n`jacket_type` may be changed to a type no other jacket of this customiser\nholds; switching to one that is taken is a 422 on `/jacket_type`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateJacket","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"jacket_type":{"type":"string"},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown jacket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The jacket's display name.","example":"Clear Jacket"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"A clear sleeve that keeps the tube colour"},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the jacket is chosen, in integer minor units.","example":500}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a jacket\n\nRemoves the jacket and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A jacket\nanything still references is refused with a 409 `resource_in_use` listing\nthe referencing rows — saved presets that chose it — and references are\nnever silently cleared. Requires `customisers:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteJacket","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["jacket_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserJacketItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown jacket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Jacket still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/{jacket_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"jacket_id","description":"The jacket id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a jacket update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateJacket","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown jacket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/jackets/{jacket_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"jacket_id","description":"The jacket id.","example":"9","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a jacket delete\n\nDry-runs the delete without removing anything: an in-use jacket returns\nthe identical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteJacket","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JacketValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown jacket","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Jacket still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser jackets"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/labels":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get customiser labels\n\nRetrieve every customer-facing label on the customiser as one canonical\ndocument. Most properties are the copy for a single piece of the storefront\ncustomiser — a step heading (`text_title`, `size_title`, `colour_title`), a\nbutton (`price_button_add`, `quote_button`), a warning\n(`char_limit_reached`, `min_height_sign_warning`) or a placeholder. A\nhandful are section switches (`show_contact_section`,\n`show_inclusion_section`, `show_review_section` and friends), which turn a\nblock of the storefront on rather than word it, and `custom_design_type` is\nthe one closed set: `none`, `link`, `form`, or `live`.\n\nThe values are the effective copy the storefront renders: your saved copy\nfor a label you have worded, and the built-in default for one you have not.\nA label reads back as null only while the customiser has no default\nlanguage, which the first write to it creates. A read is therefore a\ndescription of the storefront rather than a patch body — echo one back and\nyou would store every built-in default as an explicit override of your own,\nand its read-only properties would be refused besides. Send only the labels\nyou are changing, which is what a merge patch is for.\n\nSix properties are read-only on the merge patch. Three carry uploaded\nfiles: `product_image_url`, `background_images` (each with its `name`,\n`image_url`, and the `width_cm`/`height_cm` the photograph depicts) and\n`additional_information` (reinsurance cards, each with `header`,\n`description`, and `image_url`). A `*_url` string is a description of a\nstored file rather than a shape you could send back, so files move through\nthe staged-upload flow instead: `PUT /api/v3/customisers/{customiser_id}/labels/product-image`\nsets the product image and `DELETE` on the same address clears it. The\nother two lists have no write yet.\n\nThe other three read-only properties are the list labels: `highlights` (up\nto three short strings) and `qualities` and `custom_design_steps` (lists\nof `header`/`description` pairs). A list label's membership is shared\nbetween the customiser's stored labels and its default-language copy, and\nthe two disagree about who owns it, so a patch that shortened a list would\nbe undone before you could read it back. Rather than publish an operation\nwhose effect it cannot state, this version refuses the write. Edit the\nfive without a write in the merchant admin.\n\nThe operation is read-only, has no side effects, and is always safe to\nretry. Requires the `customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser does not\nexist on the store your API key belongs to. A 401 problem means the key is\nmissing, invalid, or expired; a 403 problem with code `insufficient_scope`\nmeans the key lacks `customisers:read`; a 429 problem means too many\nrequests — wait `retry_after` seconds, then retry. Error responses use RFC\n9457 problem+json with a machine-readable `code` and a `request_id` to\nquote when contacting support.","operationId":"getCustomiserLabels","responses":{"200":{"description":"Customiser labels","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"customiser_labels\" for this endpoint (closed set).","enum":["customiser_labels"]},"customiser_id":{"type":"integer","description":"The customiser these labels belong to."},"adding_to_cart":{"$ref":"#/components/schemas/NullableString"},"allow_custom_background":{"$ref":"#/components/schemas/NullableBoolean"},"apply_style":{"$ref":"#/components/schemas/NullableString"},"aspect_ratio_warning":{"$ref":"#/components/schemas/NullableString"},"backboard_upload_error":{"$ref":"#/components/schemas/NullableString"},"backgrounds":{"$ref":"#/components/schemas/NullableString"},"backlight_title":{"$ref":"#/components/schemas/NullableString"},"border_included":{"$ref":"#/components/schemas/NullableString"},"bottom_side":{"$ref":"#/components/schemas/NullableString"},"braille_fixed_size_info_label":{"$ref":"#/components/schemas/NullableString"},"braille_frame_too_small_message":{"$ref":"#/components/schemas/NullableString"},"braille_overview_fitted_to_screen_label":{"$ref":"#/components/schemas/NullableString"},"braille_translation_failed_message":{"$ref":"#/components/schemas/NullableString"},"braille_translation_pending_message":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"braille_untranslatable_modal_label":{"$ref":"#/components/schemas/NullableString"},"cancel":{"$ref":"#/components/schemas/NullableString"},"char_limit_reached":{"$ref":"#/components/schemas/NullableString"},"character_limit_message":{"$ref":"#/components/schemas/NullableString"},"character_text":{"$ref":"#/components/schemas/NullableString"},"click_word_desktop":{"$ref":"#/components/schemas/NullableString"},"cm_button":{"$ref":"#/components/schemas/NullableString"},"colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"colour_title":{"$ref":"#/components/schemas/NullableString"},"contact_description":{"$ref":"#/components/schemas/NullableString"},"contact_header":{"$ref":"#/components/schemas/NullableString"},"copy_button":{"$ref":"#/components/schemas/NullableString"},"create_product_failed":{"$ref":"#/components/schemas/NullableString"},"creating_product":{"$ref":"#/components/schemas/NullableString"},"crop_confirm":{"$ref":"#/components/schemas/NullableString"},"crop_modal_title":{"$ref":"#/components/schemas/NullableString"},"crop_upload":{"$ref":"#/components/schemas/NullableString"},"custom_design_button_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_file_label":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_input_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_message_success":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_submit_message":{"$ref":"#/components/schemas/NullableString"},"custom_design_form_total_size_error":{"$ref":"#/components/schemas/NullableString"},"custom_design_link":{"$ref":"#/components/schemas/NullableString"},"custom_design_steps":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignSteps"},"custom_design_steps_header":{"$ref":"#/components/schemas/NullableString"},"custom_design_tab_title":{"$ref":"#/components/schemas/NullableString"},"custom_design_type":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignType"},"custom_form_header":{"$ref":"#/components/schemas/NullableString"},"custom_sizing_message":{"$ref":"#/components/schemas/NullableString"},"default_text":{"$ref":"#/components/schemas/NullableString"},"display_initial_background_image":{"$ref":"#/components/schemas/NullableBoolean"},"display_initial_review_image":{"$ref":"#/components/schemas/NullableBoolean"},"edit_design":{"$ref":"#/components/schemas/NullableString"},"editing_word":{"$ref":"#/components/schemas/NullableString"},"etsy_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copied_button":{"$ref":"#/components/schemas/NullableString"},"etsy_copy_details_button":{"$ref":"#/components/schemas/NullableString"},"etsy_customisation_summary_label":{"$ref":"#/components/schemas/NullableString"},"etsy_download_preview_button":{"$ref":"#/components/schemas/NullableString"},"etsy_listing_url_missing":{"$ref":"#/components/schemas/NullableString"},"etsy_preparing_image":{"$ref":"#/components/schemas/NullableString"},"etsy_preview_error":{"$ref":"#/components/schemas/NullableString"},"etsy_return_button":{"$ref":"#/components/schemas/NullableString"},"etsy_step1":{"$ref":"#/components/schemas/NullableString"},"etsy_step2":{"$ref":"#/components/schemas/NullableString"},"etsy_step3":{"$ref":"#/components/schemas/NullableString"},"etsy_suggested_price_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_description":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_face_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_halo_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_icon_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_line_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_no":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_not_selected":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_side_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_title":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_trim_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_tube_colour_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_uploaded_design_label":{"$ref":"#/components/schemas/NullableString"},"etsy_summary_yes":{"$ref":"#/components/schemas/NullableString"},"example":{"$ref":"#/components/schemas/NullableString"},"extra_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"extras_title":{"$ref":"#/components/schemas/NullableString"},"file_label":{"$ref":"#/components/schemas/NullableString"},"file_upload_label":{"$ref":"#/components/schemas/NullableString"},"font_title":{"$ref":"#/components/schemas/NullableString"},"generating_image":{"$ref":"#/components/schemas/NullableString"},"header_text":{"$ref":"#/components/schemas/NullableString"},"height_input":{"$ref":"#/components/schemas/NullableString"},"highlights":{"$ref":"#/components/schemas/CustomiserColourMultiColourHexcodes"},"icon_button_label":{"$ref":"#/components/schemas/NullableString"},"icon_title":{"$ref":"#/components/schemas/NullableString"},"inch_button":{"$ref":"#/components/schemas/NullableString"},"inclusions":{"$ref":"#/components/schemas/NullableString"},"inclusions_header":{"$ref":"#/components/schemas/NullableString"},"invalid_characters":{"$ref":"#/components/schemas/NullableString"},"jacket_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"jacket_title":{"$ref":"#/components/schemas/NullableString"},"label_under_price":{"$ref":"#/components/schemas/NullableString"},"left_side":{"$ref":"#/components/schemas/NullableString"},"letter_parts_description":{"$ref":"#/components/schemas/NullableString"},"letter_parts_title":{"$ref":"#/components/schemas/NullableString"},"lightbox_product_type_title":{"$ref":"#/components/schemas/NullableString"},"line_limit_reached":{"$ref":"#/components/schemas/NullableString"},"lines_text":{"$ref":"#/components/schemas/NullableString"},"loading_manual_clear_text":{"$ref":"#/components/schemas/NullableString"},"logo_button_text":{"$ref":"#/components/schemas/NullableString"},"logo_text":{"$ref":"#/components/schemas/NullableString"},"logo_upload_design_ready":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_failed_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_file_too_large":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_email_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_name_placeholder":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submit_button":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_submitted_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_form_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_description":{"$ref":"#/components/schemas/NullableString"},"logo_upload_pending_title":{"$ref":"#/components/schemas/NullableString"},"logo_upload_try_another":{"$ref":"#/components/schemas/NullableString"},"main_tab_title":{"$ref":"#/components/schemas/NullableString"},"manual_button":{"$ref":"#/components/schemas/NullableString"},"material_mounting_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"material_title":{"$ref":"#/components/schemas/NullableString"},"max_sign_char":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_label":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_button_link":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_label":{"$ref":"#/components/schemas/NullableString"},"min_character_message":{"$ref":"#/components/schemas/NullableString"},"min_height_sign_warning":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"mounting_colour_title":{"$ref":"#/components/schemas/NullableString"},"mounting_letter_part_title":{"$ref":"#/components/schemas/NullableString"},"mounting_support_exclusion_message":{"$ref":"#/components/schemas/NullableString"},"mounting_title":{"$ref":"#/components/schemas/NullableString"},"options_tab":{"$ref":"#/components/schemas/NullableString"},"preview_note":{"$ref":"#/components/schemas/NullableString"},"price":{"$ref":"#/components/schemas/NullableString"},"price_after_label":{"$ref":"#/components/schemas/NullableString"},"price_box_text":{"$ref":"#/components/schemas/NullableString"},"price_button_add":{"$ref":"#/components/schemas/NullableString"},"price_button_finish":{"$ref":"#/components/schemas/NullableString"},"product_title":{"$ref":"#/components/schemas/NullableString"},"product_type_title":{"$ref":"#/components/schemas/NullableString"},"qualities":{"$ref":"#/components/schemas/CustomiserLabelCustomDesignSteps"},"qualities_header":{"$ref":"#/components/schemas/NullableString"},"quote_button":{"$ref":"#/components/schemas/NullableString"},"recenter":{"$ref":"#/components/schemas/NullableString"},"recenter_warning":{"$ref":"#/components/schemas/NullableString"},"retry":{"$ref":"#/components/schemas/NullableString"},"right_side":{"$ref":"#/components/schemas/NullableString"},"rotation":{"$ref":"#/components/schemas/NullableString"},"shape_finished_size":{"$ref":"#/components/schemas/NullableString"},"shape_proportions_fixed_by":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_fixed_proportions":{"$ref":"#/components/schemas/NullableString"},"shape_size_adjusted_to_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_empty_feasible_range":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_canonical_bounds":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_invalid_intent":{"$ref":"#/components/schemas/NullableString"},"shape_sizing_preset_requires_custom":{"$ref":"#/components/schemas/NullableString"},"share_design":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_description":{"$ref":"#/components/schemas/NullableString"},"share_design_modal_title":{"$ref":"#/components/schemas/NullableString"},"show_background_preview":{"$ref":"#/components/schemas/NullableBoolean"},"show_contact_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_custom_design":{"$ref":"#/components/schemas/NullableBoolean"},"show_custom_design_link":{"$ref":"#/components/schemas/NullableBoolean"},"show_inclusion_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_preview_note":{"$ref":"#/components/schemas/NullableBoolean"},"show_review_images":{"$ref":"#/components/schemas/NullableBoolean"},"show_review_section":{"$ref":"#/components/schemas/NullableBoolean"},"show_styled_note":{"$ref":"#/components/schemas/NullableBoolean"},"show_text_editor_overlay":{"$ref":"#/components/schemas/NullableBoolean"},"sign_header":{"$ref":"#/components/schemas/NullableString"},"size_title":{"$ref":"#/components/schemas/NullableString"},"slider_button":{"$ref":"#/components/schemas/NullableString"},"summary_header":{"$ref":"#/components/schemas/NullableString"},"support_colour_min_size_message":{"$ref":"#/components/schemas/NullableString"},"support_colour_title":{"$ref":"#/components/schemas/NullableString"},"support_title":{"$ref":"#/components/schemas/NullableString"},"tap_word_mobile":{"$ref":"#/components/schemas/NullableString"},"text_align_center":{"$ref":"#/components/schemas/NullableString"},"text_align_left":{"$ref":"#/components/schemas/NullableString"},"text_align_right":{"$ref":"#/components/schemas/NullableString"},"text_align_title":{"$ref":"#/components/schemas/NullableString"},"text_button_label":{"$ref":"#/components/schemas/NullableString"},"text_label":{"$ref":"#/components/schemas/NullableString"},"text_orientation_horizontal":{"$ref":"#/components/schemas/NullableString"},"text_orientation_title":{"$ref":"#/components/schemas/NullableString"},"text_orientation_vertical_stacked":{"$ref":"#/components/schemas/NullableString"},"text_title":{"$ref":"#/components/schemas/NullableString"},"top_side":{"$ref":"#/components/schemas/NullableString"},"upload_background":{"$ref":"#/components/schemas/NullableString"},"uploading_images":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_cancel":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_continue":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_description":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_error":{"$ref":"#/components/schemas/NullableString"},"user_bg_scale_title":{"$ref":"#/components/schemas/NullableString"},"visualiser_off":{"$ref":"#/components/schemas/NullableString"},"visualiser_on":{"$ref":"#/components/schemas/NullableString"},"width_input":{"$ref":"#/components/schemas/NullableString"},"zoom_in":{"$ref":"#/components/schemas/NullableString"},"zoom_out":{"$ref":"#/components/schemas/NullableString"},"additional_information":{"type":["array","null"],"description":"The reinsurance cards of the review panel, each carrying header, description, and image_url. Read-only: these files have no write on this API yet.","items":{"$ref":"#/components/schemas/CustomiserLabelAdditionalInformation"}},"background_images":{"type":["array","null"],"description":"The storefront backdrops a shopper can preview a sign against, each carrying name, image_url, and the width_cm and height_cm the photograph depicts. Read-only: these files have no write on this API yet.","items":{"$ref":"#/components/schemas/CustomiserLabelBackgroundImage"}},"product_image_url":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"$ref":"#/components/schemas/NullableString"}},"required":["object","customiser_id","additional_information","background_images","product_image_url","updated_at"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update customiser labels\n\nPartially update the customiser's labels with a JSON Merge Patch (RFC 7396)\nof the document the read operation returns: the request body is a JSON\nobject containing only the labels to change. Labels omitted from the patch\nare unchanged, a null value clears a label so the storefront falls back to\nits built-in default copy, and an empty object `{}` is a valid no-op. A\nlabel this resource does not\ndefine is refused with a JSON Pointer rather than ignored, so a typo can\nnever silently change meaning. Requires the `customisers:write` scope.\n\nNine properties the read publishes are read-only and are refused with their\nown pointer: the `object`, `customiser_id` and `updated_at` descriptors,\nthe three file-bearing labels `product_image_url`, `background_images` and\n`additional_information`, and the three list labels `highlights`,\n`qualities` and `custom_design_steps` — the read operation explains why\neach group is held back, and the refusal on `product_image_url` names the\nsub-resource that does write it. The section switches take a boolean,\nnever null.\n\nA successful update immediately changes the storefront: the customiser's\ncached configuration is invalidated and translated language content is\nresynchronised, so a label you change is queued for retranslation into\nevery language the customiser publishes. It never regenerates your Shopify\nproducts, product images, or product cache. Send an `Idempotency-Key`\nheader (1-255 visible ASCII characters, e.g. a UUID) to make the write safe\nto retry: repeating the same key with the same body replays the original\nresponse (marked `Idempotency-Replay: true`) without executing again; the\nsame key with a different body is a 409 problem with code\n`idempotency_key_conflict`. The header is optional here, as on the option\nfamilies' update operations.\n\nValidation failures are 422 problems: `validation_failed` lists every field\nerror in `errors[]` with a JSON `pointer` and, for closed-set violations,\nthe `allowed_values`; `unknown_field` means the body names labels outside\nthe document. A label whose current copy carries placeholder tokens (such\nas `{width}` or `{price}`) must keep them, or the write is refused on that\nlabel's pointer — the storefront substitutes real values into those tokens\nand dropping one would print the placeholder to a shopper. A 400 problem\nmeans the body is not valid JSON or the `Idempotency-Key` header is\nmalformed. 401/403/404/429 behave as on the read operation, with\n`customisers:write` as the required scope.","operationId":"updateCustomiserLabels","parameters":[{"in":"header","name":"Idempotency-Key","example":"d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70","schema":{"type":"string"}}],"responses":{"200":{"description":"Customiser labels","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency key conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/labels/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate customiser labels\n\nDry-run a labels merge patch without persisting anything. The request body\nis the same merge patch the update operation accepts, and the response is\nexactly what that update would produce on the failing paths: the identical\n422 problem (`validation_failed` with pointers and `allowed_values`, or\n`unknown_field`) — the same validation code path runs, only persistence is\nskipped. On a patch that would apply, the response is a 200\n`customiser_labels_validation` confirmation. Nothing is written, no caches\nare touched, no idempotency key is consumed, and no activity is recorded,\nso the operation is always safe to repeat. The verdict is advisory: nothing\nbinds it to a later update, and the labels can legitimately change between\nthe two calls. Requires the `customisers:write` scope (it is the write's\ndry-run twin).\n\nA 400 problem means the body is not valid JSON; a 422 problem with code\n`invalid_document` means the body is not a JSON object. 401/403/404/429\nbehave as on the read operation, with `customisers:write` as the required\nscope.","operationId":"validateUpdateCustomiserLabels","responses":{"200":{"description":"Patch would apply","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The verdict type discriminator (closed set).","enum":["customiser_labels_validation"]},"operation":{"$ref":"#/components/schemas/LabelValidateUpdateOperation"},"customiser_id":{"type":"integer"},"valid":{"type":"boolean","description":"Always true — a patch that would fail returns the update's own error response instead of a 200."}},"required":["object","operation","valid"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/labels/product-image":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"put":{"summary":"Attach the labels product image\n\nTakes the raster image of a staged upload and makes it the labels\ndocument's product image — the picture shown beside the product title in\nthe summary — returning the labels with `product_image_url` pointing at\nthe new file. Requires `customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` using purpose\n`label_image`, send the bytes to the presigned target it returns, then\nsend that upload's id here. An upload whose purpose is not `label_image`\nanswers 422 `upload_purpose_mismatch`. The upload is single use:\nattaching it a second time answers 409.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the label image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nThe product image is the only file this document publishes a write for,\nwhich is why this operation names it rather than taking a slot parameter.\n`background_images` and `additional_information` carry files too and\nremain read-only; when either gets a write it will get its own address.\n\nAttaching over an image that is already there replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for\nit stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache. A customiser that has never had its\nlabels edited gets its labels row here, exactly as its first label write\nwould create one.","operationId":"attachCustomiserLabelProductImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"e5f6a7b8-9c0d-4e1f-8a2b-3c4d5e6f7a8b","schema":{"type":"string"}}],"responses":{"200":{"description":"Customiser labels","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete the labels product image\n\nClears the labels document's product image and deletes the stored file,\nreturning a receipt naming what was removed and the labels as they now\nstand. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nwhen no product image is set answers 404.\n\nThe product image decorates the summary and nothing renders from it, so\nthe delete is never refused for renderability: a document without one is\nthe state every customiser starts in.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteCustomiserLabelProductImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"f6a7b8c9-0d1e-4f2a-9b3c-4d5e6f7a8b9c","schema":{"type":"string"}}],"responses":{"200":{"description":"Product image removed","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string","description":"The file slot that was cleared. Always the product image on this operation, and named for consistency with every other file deletion receipt.","enum":["product_image"]},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"adding_to_cart":{"type":"string"},"allow_custom_background":{"type":"boolean"},"apply_style":{"type":"string"},"aspect_ratio_warning":{"type":"string"},"backboard_upload_error":{"type":"string"},"backgrounds":{"type":"string"},"backlight_title":{"type":"string"},"border_included":{"type":"string"},"bottom_side":{"type":"string"},"braille_fixed_size_info_label":{"type":"string"},"braille_frame_too_small_message":{"type":"string"},"braille_overview_fitted_to_screen_label":{"type":"string"},"braille_translation_failed_message":{"type":"string"},"braille_translation_pending_message":{"type":"string"},"braille_untranslatable_modal_button_label":{"type":"string"},"braille_untranslatable_modal_label":{"type":"string"},"cancel":{"type":"string"},"char_limit_reached":{"type":"string"},"character_limit_message":{"type":"string"},"character_text":{"type":"string"},"click_word_desktop":{"type":"string"},"cm_button":{"type":"string"},"colour_min_size_message":{"type":"string"},"colour_title":{"type":"string"},"contact_description":{"type":"string"},"contact_header":{"type":"string"},"copy_button":{"type":"string"},"create_product_failed":{"type":"string"},"creating_product":{"type":"string"},"crop_confirm":{"type":"string"},"crop_modal_title":{"type":"string"},"crop_upload":{"type":"string"},"custom_design_button_label":{"type":"string"},"custom_design_form_file_label":{"type":"string"},"custom_design_form_input_error":{"type":"string"},"custom_design_form_message_error":{"type":"string"},"custom_design_form_message_success":{"type":"string"},"custom_design_form_size_error":{"type":"string"},"custom_design_form_submit_message":{"type":"string"},"custom_design_form_total_size_error":{"type":"string"},"custom_design_link":{"$ref":"#/components/schemas/NullableString"},"custom_design_steps":{"$ref":"#/components/schemas/LabelProductImageResourceCustomDesignSteps"},"custom_design_steps_header":{"type":"string"},"custom_design_tab_title":{"type":"string"},"custom_design_type":{"type":"string"},"custom_form_header":{"$ref":"#/components/schemas/NullableString"},"custom_sizing_message":{"type":"string"},"default_text":{"type":"string"},"display_initial_background_image":{"type":"boolean"},"display_initial_review_image":{"type":"boolean"},"edit_design":{"type":"string"},"editing_word":{"type":"string"},"etsy_button":{"type":"string"},"etsy_copied_button":{"type":"string"},"etsy_copy_details_button":{"type":"string"},"etsy_customisation_summary_label":{"type":"string"},"etsy_download_preview_button":{"type":"string"},"etsy_listing_url_missing":{"type":"string"},"etsy_preparing_image":{"type":"string"},"etsy_preview_error":{"type":"string"},"etsy_return_button":{"type":"string"},"etsy_step1":{"type":"string"},"etsy_step2":{"type":"string"},"etsy_step3":{"type":"string"},"etsy_suggested_price_label":{"type":"string"},"etsy_summary_colour_label":{"type":"string"},"etsy_summary_description":{"type":"string"},"etsy_summary_design_label":{"type":"string"},"etsy_summary_face_colour_label":{"type":"string"},"etsy_summary_halo_colour_label":{"type":"string"},"etsy_summary_icon_label":{"type":"string"},"etsy_summary_line_label":{"type":"string"},"etsy_summary_no":{"type":"string"},"etsy_summary_not_selected":{"type":"string"},"etsy_summary_side_colour_label":{"type":"string"},"etsy_summary_title":{"type":"string"},"etsy_summary_trim_colour_label":{"type":"string"},"etsy_summary_tube_colour_label":{"type":"string"},"etsy_summary_uploaded_design_label":{"type":"string"},"etsy_summary_yes":{"type":"string"},"example":{"type":"string"},"extra_colour_exclusion_message":{"type":"string"},"extras_title":{"type":"string"},"file_label":{"type":"string"},"file_upload_label":{"type":"string"},"font_title":{"type":"string"},"generating_image":{"type":"string"},"header_text":{"type":"string"},"height_input":{"type":"string"},"highlights":{"$ref":"#/components/schemas/OrderProducts"},"icon_button_label":{"type":"string"},"icon_title":{"type":"string"},"inch_button":{"type":"string"},"inclusions":{"type":"string"},"inclusions_header":{"type":"string"},"invalid_characters":{"type":"string"},"jacket_colour_exclusion_message":{"type":"string"},"jacket_title":{"type":"string"},"label_under_price":{"type":"string"},"left_side":{"type":"string"},"letter_parts_description":{"type":"string"},"letter_parts_title":{"type":"string"},"lightbox_product_type_title":{"type":"string"},"line_limit_reached":{"type":"string"},"lines_text":{"type":"string"},"loading_manual_clear_text":{"type":"string"},"logo_button_text":{"type":"string"},"logo_text":{"type":"string"},"logo_upload_design_ready":{"type":"string"},"logo_upload_failed_description":{"type":"string"},"logo_upload_failed_title":{"type":"string"},"logo_upload_file_too_large":{"type":"string"},"logo_upload_form_description":{"type":"string"},"logo_upload_form_email_placeholder":{"type":"string"},"logo_upload_form_name_placeholder":{"type":"string"},"logo_upload_form_submit_button":{"type":"string"},"logo_upload_form_submitted_description":{"type":"string"},"logo_upload_form_submitted_title":{"type":"string"},"logo_upload_form_title":{"type":"string"},"logo_upload_pending_description":{"type":"string"},"logo_upload_pending_title":{"type":"string"},"logo_upload_try_another":{"type":"string"},"main_tab_title":{"type":"string"},"manual_button":{"type":"string"},"material_mounting_exclusion_message":{"type":"string"},"material_support_exclusion_message":{"type":"string"},"material_title":{"type":"string"},"max_sign_char":{"type":"string"},"max_sign_modal_button_label":{"type":"string"},"max_sign_modal_button_link":{"$ref":"#/components/schemas/NullableString"},"max_sign_modal_label":{"type":"string"},"min_character_message":{"type":"string"},"min_height_sign_warning":{"type":"string"},"mounting_colour_exclusion_message":{"type":"string"},"mounting_colour_min_size_message":{"type":"string"},"mounting_colour_title":{"type":"string"},"mounting_letter_part_title":{"type":"string"},"mounting_support_exclusion_message":{"type":"string"},"mounting_title":{"type":"string"},"options_tab":{"type":"string"},"preview_note":{"type":"string"},"price":{"type":"string"},"price_after_label":{"type":"string"},"price_box_text":{"type":"string"},"price_button_add":{"type":"string"},"price_button_finish":{"type":"string"},"product_title":{"type":"string"},"product_type_title":{"type":"string"},"qualities":{"$ref":"#/components/schemas/LabelProductImageResourceCustomDesignSteps"},"qualities_header":{"type":"string"},"quote_button":{"type":"string"},"recenter":{"type":"string"},"recenter_warning":{"type":"string"},"retry":{"type":"string"},"right_side":{"type":"string"},"rotation":{"type":"string"},"shape_finished_size":{"type":"string"},"shape_proportions_fixed_by":{"type":"string"},"shape_size_adjusted_to_fixed_proportions":{"type":"string"},"shape_size_adjusted_to_range":{"type":"string"},"shape_sizing_empty_feasible_range":{"type":"string"},"shape_sizing_invalid_canonical_bounds":{"type":"string"},"shape_sizing_invalid_intent":{"type":"string"},"shape_sizing_preset_requires_custom":{"type":"string"},"share_design":{"type":"string"},"share_design_modal_description":{"type":"string"},"share_design_modal_title":{"type":"string"},"show_background_preview":{"type":"boolean"},"show_contact_section":{"type":"boolean"},"show_custom_design":{"type":"boolean"},"show_custom_design_link":{"type":"boolean"},"show_inclusion_section":{"type":"boolean"},"show_preview_note":{"type":"boolean"},"show_review_images":{"type":"boolean"},"show_review_section":{"type":"boolean"},"show_styled_note":{"type":"boolean"},"show_text_editor_overlay":{"type":"boolean"},"sign_header":{"type":"string"},"size_title":{"type":"string"},"slider_button":{"type":"string"},"summary_header":{"type":"string"},"support_colour_min_size_message":{"type":"string"},"support_colour_title":{"type":"string"},"support_title":{"type":"string"},"tap_word_mobile":{"type":"string"},"text_align_center":{"type":"string"},"text_align_left":{"type":"string"},"text_align_right":{"type":"string"},"text_align_title":{"type":"string"},"text_button_label":{"type":"string"},"text_label":{"type":"string"},"text_orientation_horizontal":{"type":"string"},"text_orientation_title":{"type":"string"},"text_orientation_vertical_stacked":{"type":"string"},"text_title":{"type":"string"},"top_side":{"type":"string"},"upload_background":{"type":"string"},"uploading_images":{"type":"string"},"user_bg_scale_cancel":{"type":"string"},"user_bg_scale_continue":{"type":"string"},"user_bg_scale_description":{"type":"string"},"user_bg_scale_error":{"type":"string"},"user_bg_scale_title":{"type":"string"},"visualiser_off":{"type":"string"},"visualiser_on":{"type":"string"},"width_input":{"type":"string"},"zoom_in":{"type":"string"},"zoom_out":{"type":"string"},"additional_information":{"$ref":"#/components/schemas/CustomiserLabelAdditionalInformation2"},"background_images":{"$ref":"#/components/schemas/CustomiserLabelBackgroundImages"},"product_image_url":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"type":"string"}},"description":"The labels document as it now stands, with product_image_url null. Unlike a row deletion receipt, this describes the state after the delete, because the document survives it."}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No product image set","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/labels/product-image/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a labels product image attach\n\nDry-runs the attach: the upload must exist for this store and this API\nkey, and its purpose must be `label_image`. Nothing is written, the\nupload is not consumed, no labels row is created, and no idempotency key\nis spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachCustomiserLabelProductImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/labels/product-image/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a labels product image delete\n\nDry-runs the delete: a document with no product image answers the\nidentical 404 the delete would, and one with an image returns a 200\nverdict. Nothing is written, nothing is removed, and no idempotency key\nis consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteCustomiserLabelProductImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"No product image set","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser labels"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List customiser languages\n\nList the customiser's languages in the order they were added. Each language\ncarries its `status`, whether it `is_default`, a `translation_summary` of how\nmuch of its copy is written, and its most recent `ai_translation` if it has\never had one. `publishable` inside the summary is the copy gate: it is true\nwhen no required key is still blank. A running AI translation blocks the\npublish separately, which `ai_translation` reports.\n\n`meta.language_sync` is the customiser-level state of the advisory catalogue\nsync every option write triggers. That sync adds translation rows for new\ncopy, retires rows for copy that is gone, and marks drifted values stale — it\nruns after the triggering write has committed and cannot fail it, so it is\neventually consistent by design. `status` is `not_run` before the customiser\nhas ever been synchronised, and otherwise the outcome the last sync recorded:\n`synchronised`, or `failed` when it did not converge.\n`languages_version` changes whenever the published payload does, so an\nintegration can cache against it.\n\nThe operation is read-only, has no side effects, and is always safe to\nretry. Requires the `customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser does not\nexist on the store your API key belongs to. A 401 problem means the key is\nmissing, invalid, or expired; a 403 problem with code `insufficient_scope`\nmeans the key lacks `customisers:read`; a 422 problem means an unknown query\nparameter or an invalid cursor; a 429 problem means too many requests — wait\n`retry_after` seconds, then retry. Error responses use RFC 9457 problem+json\nwith a machine-readable `code` and a `request_id` to quote when contacting\nsupport.","operationId":"listCustomiserLanguages","parameters":[{"in":"query","name":"limit","description":"The page size, 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"The page size, 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"The opaque next_cursor from a previous page.","example":"eyJ2IjoxfQ","required":false,"schema":{"type":"string","description":"The opaque next_cursor from a previous page."}}],"responses":{"200":{"description":"Customiser languages","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLanguageItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"type":"object","properties":{"api_version":{"type":"string"},"request_id":{"type":"string"},"language_sync":{"type":"object","properties":{"languages_version":{"type":"string"},"synced_at":{"type":"string"},"status":{"type":"string"}},"description":"The customiser-level state of the advisory language sync: languages_version, synced_at, and status (not_run, synchronised, or failed). Eventually consistent — the sync runs after the write that triggered it has committed."}},"required":["language_sync"]}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a customiser language\n\nAdd a language to the customiser. The language starts as a draft with one\nblank translation row per catalogue key: nothing a shopper sees changes until\nyou write its required copy and publish it. `language_code` is an IETF\nlanguage tag and is normalised to its canonical form, so `fr_ca` and `FR-ca`\nboth create `fr-CA`. `source_language_code` names the language this one is\ntranslated from and defaults to the customiser default language;\n`display_name` defaults to the tag own language name.\n\nA create is refused with a 422 `validation_failed` when the tag is malformed,\nwhen the customiser already has that language, or when `source_language_code`\nnames a language it does not have. The new language is never the default:\nmaking it the default is a separate operation that requires it to be\npublished and complete first.\n\nThe create clears the customiser cached storefront configuration and runs the\nadvisory language sync, which is what seeds the translation rows. It never\nregenerates your Shopify products, product images, or product cache. An\n`Idempotency-Key` header (1-255 visible ASCII characters, e.g. a UUID) is\nrequired: repeating the same key with the same body replays the original\nresponse (marked `Idempotency-Replay: true`) without creating a second\nlanguage, and the same key with a different body is a 409 problem with code\n`idempotency_key_conflict`. Requires the `customisers:write` scope.\n\nA 400 problem means the body is not valid JSON, or the `Idempotency-Key`\nheader is missing or malformed. A 422 problem with code `unknown_field`\nmeans the body names properties this operation does not define. 401, 403,\n404, and 429 problems behave as on the list operation, with\n`customisers:write` as the required scope.","operationId":"createCustomiserLanguage","parameters":[{"in":"header","name":"Idempotency-Key","example":"2f1c8a90-6b1e-4c3a-9d7f-8e0a1b2c3d4e","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"Language created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLanguageResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"409":{"description":"Idempotency key conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Duplicate language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}}},"tags":["Customiser languages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"language_code":{"type":"string","description":"The IETF language tag to add, normalised to its canonical form. The customiser must not already have it.","example":"fr-CA"},"source_language_code":{"type":"string","description":"The language this one is translated from. Must be a language the customiser already has. Defaults to the customiser default language.","example":"en"},"display_name":{"type":"string","description":"How the language is named in the merchant admin. Defaults to the language tag own name.","example":"Canadian French"}},"required":["language_code"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser language\n\nDry-run a language create without persisting anything. The request body is\nthe same body the create accepts, and the response on the failing paths is\nexactly the problem the create would return — the same validation runs, only\npersistence is skipped. On a body that would apply, the response is a 200\n`customiser_language_validation` confirmation. Nothing is written, no caches\nare touched, no idempotency key is consumed, and no activity is recorded, so\nthe operation is always safe to repeat.\n\nThe verdict is advisory: nothing binds it to a later create, and the\ncustomiser languages can legitimately change between the two calls. Requires\nthe `customisers:write` scope, because it is the write dry-run twin.\n\nA 400 problem means the body is not valid JSON; a 422 problem carries the\ncreate own `validation_failed` or `unknown_field` diagnostics. 401, 403, 404,\nand 429 problems behave as on the create operation.","operationId":"validateCreateCustomiserLanguage","responses":{"200":{"description":"Create would apply","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The verdict type discriminator (closed set).","enum":["customiser_language_validation"]},"operation":{"type":"string","description":"The write this verdict is the dry run of (closed set).","enum":["create","delete","publish","make_default","cancel"]},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"valid":{"type":"boolean","description":"Always true — a write that would fail returns its own error response instead of a 200."}},"required":["object","operation","valid"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Duplicate language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}}},"tags":["Customiser languages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"language_code":{"type":"string","description":"The same body the create operation takes.","example":"fr-CA"}},"required":["language_code"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get a customiser language\n\nRetrieve one language of the customiser by its language code. The response is\nthe same resource the list operation returns, including the completeness\nsummary and the most recent AI translation. The operation is read-only, has\nno side effects, and is always safe to retry. Requires the\n`customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser or the\nlanguage does not exist on the store your API key belongs to — a code that is\nnot a well-formed language tag, a language on another customiser, and a\nlanguage on another store are all the same 404. 401, 403, and 429 problems\nbehave as on the list operation.","operationId":"getCustomiserLanguage","responses":{"200":{"description":"Customiser language","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLanguageResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:read"]}]},"delete":{"summary":"Delete a customiser language\n\nRemove a language and every translation row it holds, and return a receipt:\nthe language exactly as it was immediately before deletion. The copy is gone\nfrom the storefront as soon as the customiser cached configuration clears.\n\nTwo states refuse the delete. A 422 problem with code\n`language_not_deletable` means it is the customiser default language, or the\nonly published one: a customiser must always keep one published default,\nbecause it is the copy the storefront falls back to — publish another\nlanguage and make it the default first. A 409 problem with code\n`ai_translation_in_progress` means an AI translation is still writing this\nlanguage draft copy; wait for it, or cancel it, then retry. A 409 problem\nwith code `resource_in_use` means other records still point at the language,\nand names them in `referenced_by`.\n\nThe delete clears the customiser cached storefront configuration and runs the\nadvisory language sync. It never regenerates your Shopify products, product\nimages, or product cache. An `Idempotency-Key` header is required:\nrepeating the same key replays the original receipt without deleting\nanything else. Requires the `customisers:write` scope.\n\nA 400 problem means the `Idempotency-Key` header is missing or malformed.\n401, 403, 404, and 429 problems behave as on the read operation, with\n`customisers:write` as the required scope.","operationId":"deleteCustomiserLanguage","parameters":[{"in":"header","name":"Idempotency-Key","example":"7a3b4c5d-6e7f-4081-92a3-b4c5d6e7f809","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Language deleted","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["customiser_language_deletion_receipt"]},"language_code":{"type":"string","description":"The language that was removed. Receipts on other families carry an id; a language identity is its code."},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserLanguageData"}},"required":["object","language_code","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"AI translation in progress","content":{"application/problem+json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"ai_translation_statuses":{"$ref":"#/components/schemas/OrderProducts"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]}}}},"422":{"description":"Sole published default language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate deleting a customiser language\n\nDry-run a language delete without removing anything. The response on the\nfailing paths is exactly the problem the delete would return — the same\nlifecycle guard and reference check run, only the removal is skipped. On a\nlanguage that would delete, the response is a 200\n`customiser_language_validation` confirmation. Nothing is written, no caches\nare touched, no idempotency key is consumed, and no activity is recorded.\n\nThe verdict is advisory: nothing binds it to a later delete, and the language\ncan legitimately change between the two calls. Requires the\n`customisers:write` scope, because it is the write dry-run twin.\n\n401, 403, 404, 409, 422, and 429 problems behave exactly as on the delete\noperation.","operationId":"validateDeleteCustomiserLanguage","responses":{"200":{"description":"Delete would apply","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageValidateCancelAiTranslationResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Sole published default language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/publish":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Publish a customiser language\n\nPromote the language draft copy to its published copy, which is what the\nstorefront serves. Publishing is the only operation that changes what a\nshopper sees: writing draft values never does.\n\nThe gate is required copy. A 422 problem with code\n`language_publish_blocked` lists every required key whose draft value is\nstill blank in `missing_keys`; write those, then retry. Stale values — copy\nwhose source has changed since it was translated — do not block\npublishing: they publish, stay flagged as stale on the translations read, and\nremain reviewable. A 409 problem with code `ai_translation_in_progress`\nmeans an AI translation is still writing the draft; wait for it, or cancel\nit, then retry.\n\nPublishing is idempotent in the domain sense as well as the protocol sense:\npublishing an already-published language re-promotes its current draft\nvalues, which is how a merchant pushes reviewed changes live. The write\nclears the customiser cached storefront configuration and runs the advisory\nlanguage sync; it never regenerates your Shopify products, product images, or\nproduct cache. An `Idempotency-Key` header is required. Requires the\n`customisers:write` scope.\n\nA 400 problem means the `Idempotency-Key` header is missing or malformed.\n401, 403, 404, and 429 problems behave as on the read operation, with\n`customisers:write` as the required scope.","operationId":"publishCustomiserLanguage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c9d0e1-f2a3-4b45-8c67-d8e9f0a1b2c3","schema":{"type":"string"}}],"responses":{"200":{"description":"Language published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageDefaultResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Required copy missing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguagePublishResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/validate-publish":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate publishing a customiser language\n\nDry-run a language publish without promoting anything. The response on the\nfailing paths is exactly the problem the publish would return, including the\n`missing_keys` list, so an agent can report precisely what is still needed\nbefore committing. On a language that would publish, the response is a 200\n`customiser_language_validation` confirmation. Nothing is written, no caches\nare touched, no idempotency key is consumed, and no activity is recorded.\n\nThe verdict is advisory: nothing binds it to a later publish, and the draft\ncopy can legitimately change between the two calls. Requires the\n`customisers:write` scope, because it is the write dry-run twin.\n\n401, 403, 404, 409, 422, and 429 problems behave exactly as on the publish\noperation.","operationId":"validatePublishCustomiserLanguage","responses":{"200":{"description":"Publish would apply","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageValidateCancelAiTranslationResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Required copy missing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguagePublishResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/default":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Set the default customiser language\n\nMake this language the customiser default: the source every other language is\ntranslated from, and the copy the storefront falls back to when a shopper\nlocale has no published language. Exactly one language carries it, so the\nprevious default gives it up in the same transaction.\n\nA 422 problem with code `language_not_defaultable` means the language is not\neligible: `reason` is `not_published` when it has never been published, or\n`incomplete_published_translations` when its published copy is missing a\nrequired key or has drifted from its source. Publish it first, then retry.\nSetting the default on the language that already holds it is accepted and\nchanges nothing.\n\nThe write clears the customiser cached storefront configuration and runs the\nadvisory language sync. It never regenerates your Shopify products, product\nimages, or product cache. An `Idempotency-Key` header is required.\nRequires the `customisers:write` scope.\n\nA 400 problem means the `Idempotency-Key` header is missing or malformed.\n401, 403, 404, and 429 problems behave as on the read operation, with\n`customisers:write` as the required scope.","operationId":"setDefaultCustomiserLanguage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d0e1f2a3-b4c5-4d67-8e90-a1b2c3d4e5f6","schema":{"type":"string"}}],"responses":{"200":{"description":"Default language set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageDefaultResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Language not published","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/validate-default":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate setting the default customiser language\n\nDry-run a set-default without changing anything. The response on the failing\npath is exactly the `language_not_defaultable` problem the write would\nreturn, with the same `reason`. On a language that would become the default,\nthe response is a 200 `customiser_language_validation` confirmation. Nothing\nis written, no caches are touched, no idempotency key is consumed, and no\nactivity is recorded.\n\nThe verdict is advisory: nothing binds it to a later write, and the language\ncan legitimately change between the two calls. Requires the\n`customisers:write` scope, because it is the write dry-run twin.\n\n401, 403, 404, 422, and 429 problems behave exactly as on the set-default\noperation.","operationId":"validateSetDefaultCustomiserLanguage","responses":{"200":{"description":"Set default would apply","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageValidateCancelAiTranslationResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Language not published","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/cancel-ai-translation":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Cancel an AI translation\n\nStop the AI translation currently writing this language draft copy. The job\nis marked cancelled, the provider batch behind it is cancelled too, and a\nlanguage left in the `translating` status returns to `draft`. Draft values the\ntranslation had already written are kept: cancelling stops further work, it\ndoes not undo work already done.\n\nThis is the only AI translation operation on the API. Starting and retrying a\ntranslation are deferred, because each call spends money with an external\nprovider on an unbounded amount of copy — so this operation exists to stop a\ntranslation the merchant admin started, which is what unblocks a publish or a\ndelete refused with `ai_translation_in_progress`.\n\nA 422 problem with code `ai_translation_not_cancellable` means there is\nnothing to cancel: `reason` is `no_translation` when the language has never\nhad one, or `not_running` when its most recent translation has already\nfinished, failed, or been cancelled. Read the language `ai_translation` to\nsee which.\n\nThe write clears the customiser cached storefront configuration and runs the\nadvisory language sync. It never regenerates your Shopify products, product\nimages, or product cache. An `Idempotency-Key` header is required.\nRequires the `customisers:write` scope.\n\nA 400 problem means the `Idempotency-Key` header is missing or malformed.\n401, 403, 404, and 429 problems behave as on the read operation, with\n`customisers:write` as the required scope.","operationId":"cancelLanguageAiTranslation","parameters":[{"in":"header","name":"Idempotency-Key","example":"f2a3b4c5-d6e7-4f89-90a1-b2c3d4e5f607","schema":{"type":"string"}}],"responses":{"200":{"description":"AI translation cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"source_language_code":{"type":"string"},"display_name":{"type":"string"},"status":{"type":"string"},"is_default":{"type":"boolean"},"translation_summary":{"$ref":"#/components/schemas/CustomiserLanguageItemTranslationSummary"},"ai_translation":{"type":"object","properties":{"object":{"type":"string"},"status":{"type":"string","description":"The cancelled translation status (closed set). Always cancelled on a successful cancel.","enum":["queued","running","completed","failed","cancelled"]},"cancellable":{"type":"boolean"},"chunks_total":{"type":"integer"},"keys_targeted":{"type":"integer"},"error_summary":{"$ref":"#/components/schemas/NullableString"},"started_at":{"type":"string"},"finished_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["status"]},"published_at":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Nothing to cancel","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/validate-cancel-ai-translation":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate cancelling an AI translation\n\nDry-run an AI translation cancel without stopping anything. The response on\nthe failing path is exactly the `ai_translation_not_cancellable` problem the\nwrite would return, with the same `reason`. On a translation that would\ncancel, the response is a 200 `customiser_language_validation` confirmation.\nNothing is written, no provider batch is touched, no idempotency key is\nconsumed, and no activity is recorded.\n\nThe verdict is advisory: nothing binds it to a later cancel, and a running\ntranslation can legitimately finish between the two calls. Requires the\n`customisers:write` scope, because it is the write dry-run twin.\n\n401, 403, 404, 422, and 429 problems behave exactly as on the cancel\noperation.","operationId":"validateCancelLanguageAiTranslation","responses":{"200":{"description":"Cancel would apply","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageValidateCancelAiTranslationResponse"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Nothing to cancel","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageCancelAiTranslationResponse"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/translations":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"get":{"summary":"List language translations\n\nList the translation rows of one customiser language, in catalogue order —\nthe order the merchant admin editor presents them in, which groups related\ncopy together and stays stable across pages.\n\nEvery row carries two phases. The `draft_*` members are what a merchant or an\nAI translation is working on; the `published_*` members are what the\nstorefront serves. Only publishing the language promotes one to the other.\n`source_text` is the copy being translated, in the language named by\n`source_language_code` on the language itself. `draft_stale` and\n`published_stale` are true when the value is filled but the source has\nchanged since it was written: the value still serves and still publishes, it\nsimply no longer matches what it was translated from.\n\n`retired` rows are keys whose copy no longer exists. They are kept for\nhistory, excluded from publishing and from AI work, and refused on write —\nuse `filter[retired]=false` to leave them out entirely.\n\nThe operation is read-only, has no side effects, and is always safe to retry.\nRequires the `customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser or the\nlanguage does not exist on the store your API key belongs to. A 401 problem\nmeans the key is missing, invalid, or expired; a 403 problem with code\n`insufficient_scope` means the key lacks `customisers:read`; a 422 problem\nmeans an unknown query parameter, an unknown filter, or an invalid cursor; a\n429 problem means too many requests — wait `retry_after` seconds, then retry.","operationId":"listLanguageTranslations","parameters":[{"in":"query","name":"limit","description":"The page size, 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"The page size, 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"The opaque next_cursor from a previous page.","example":"eyJ2IjoxfQ","required":false,"schema":{"type":"string","description":"The opaque next_cursor from a previous page."}},{"in":"query","name":"filter[group]","description":"Only rows in one catalogue group.","example":"selections","required":false,"schema":{"type":"string","description":"Only rows in one catalogue group."}},{"in":"query","name":"filter[key]","description":"Only the row with this exact catalogue key.","example":"selection:12:label","required":false,"schema":{"type":"string","description":"Only the row with this exact catalogue key."}},{"in":"query","name":"filter[required]","description":"Only required (true) or only optional (false) rows.","example":"true","required":false,"schema":{"type":"string","description":"Only required (true) or only optional (false) rows."}},{"in":"query","name":"filter[retired]","description":"Only retired (true) or only live (false) rows.","example":"false","required":false,"schema":{"type":"string","description":"Only retired (true) or only live (false) rows."}}],"responses":{"200":{"description":"Language translations","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"key":{"type":"string"},"group":{"type":"string"},"required":{"type":"boolean"},"retired":{"type":"boolean"},"source_text":{"type":"string"},"draft_value":{"type":"string"},"draft_status":{"type":"string"},"draft_origin":{"type":"string"},"draft_stale":{"type":"boolean"},"published_value":{"$ref":"#/components/schemas/NullableString"},"published_status":{"type":"string"},"published_origin":{"$ref":"#/components/schemas/NullableString"},"published_stale":{"type":"boolean"},"published_at":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"type":"string"}}}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown language","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}}},"tags":["Customiser languages"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update language translations\n\nWrite draft values for the language. The body names the keys to change and\nthe value to give each of them; a key the body does not name keeps the value\nit has, and `draft_value: null` clears one back to blank. Nothing a shopper\nsees changes: publishing the language is what promotes draft values to\npublished copy.\n\nOnly `draft_value` is writable. Every other property the read publishes is\nserver-owned — the catalogue supplies the key, the group, whether it is\nrequired, and the source copy; a publish supplies the published values; the\nsync supplies the staleness — so naming one is a 422 pointing at it rather\nthan a value silently dropped. A key may appear once; a second entry for the\nsame key is refused rather than resolved.\n\nA 422 problem with code `invalid_translation_key` means the body names keys\nthis language cannot accept, with a pointer per offending entry and a\n`reason` of `unknown` (the catalogue has never carried it) or `retired` (the\ncopy it translated is gone). A 422 `validation_failed` with a pointer at\n`/translations/{index}/draft_value` means a value dropped a placeholder its\nsource copy requires — a value that loses `{price}` would render a broken\nsentence in the storefront.\n\nThe write clears the customiser cached storefront configuration and runs the\nadvisory language sync. It never regenerates your Shopify products, product\nimages, or product cache. Send an `Idempotency-Key` header (1-255 visible\nASCII characters, e.g. a UUID) to make the write safe to retry: repeating the\nsame key with the same body replays the original response (marked\n`Idempotency-Replay: true`) without executing again; the same key with a\ndifferent body is a 409 problem with code `idempotency_key_conflict`. The\nheader is optional here, as on every update operation. Requires the\n`customisers:write` scope.\n\nA 400 problem means the body is not valid JSON or the `Idempotency-Key`\nheader is malformed; a 422 problem with code `unknown_field` means the body\nnames properties this operation does not define. 401, 403, 404, and 429\nproblems behave as on the read operation, with `customisers:write` as the\nrequired scope.","operationId":"updateLanguageTranslations","parameters":[{"in":"header","name":"Idempotency-Key","example":"3c4d5e6f-7081-4923-a4b5-c6d7e8f90a1b","schema":{"type":"string"}}],"responses":{"200":{"description":"Translations written","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["customiser_language_translations_write_receipt"]},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"written_key_count":{"type":"integer","description":"How many of the named keys were given a value."},"cleared_key_count":{"type":"integer","description":"How many of the named keys were cleared back to blank."},"missing_required_key_count":{"type":"integer"},"publishable":{"type":"boolean","description":"Whether the language would now publish: true exactly when missing_required_key_count is zero."},"keys":{"$ref":"#/components/schemas/OrderProducts"}},"required":["object","written_key_count","cleared_key_count","publishable"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown or retired key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageTranslationResponse"}}}}},"tags":["Customiser languages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"translations":{"type":"array","description":"The keys to write, each with the draft value to give it. A key the list does not name is unchanged; a key may appear once.","example":[[]],"items":{"type":"object","properties":{"key":{"type":"string","description":"A catalogue key this language carries, from the list language translations operation. Unknown and retired keys are refused.","example":"selection:12:label"},"draft_value":{"type":"string","description":"The draft value to write. Null clears it back to blank, which makes a required key block publishing again.","example":"Couleur"}},"required":["key","draft_value"]}}},"required":["translations"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/languages/{language_code}/translations/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"language_code","description":"The language tag, from the list customiser languages operation.","example":"fr","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate language translations\n\nDry-run a translation write without persisting anything. The request body is\nthe same body the update accepts, and the response on the failing paths is\nexactly the problem that update would return — the same key allowlist and the\nsame placeholder checks run, only persistence is skipped. On a body that\nwould apply, the response is a 200\n`customiser_language_translations_validation` confirmation. Nothing is\nwritten, no caches are touched, no idempotency key is consumed, and no\nactivity is recorded, so the operation is always safe to repeat.\n\nThe verdict is advisory: nothing binds it to a later update, and the\ncatalogue can legitimately change between the two calls. Requires the\n`customisers:write` scope, because it is the write dry-run twin.\n\nA 400 problem means the body is not valid JSON; 422 problems carry the\nupdate own `invalid_translation_key`, `validation_failed`, or `unknown_field`\ndiagnostics. 401, 403, 404, and 429 problems behave as on the update\noperation.","operationId":"validateUpdateLanguageTranslations","responses":{"200":{"description":"Write would apply","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The verdict type discriminator (closed set).","enum":["customiser_language_translations_validation"]},"operation":{"$ref":"#/components/schemas/LabelValidateUpdateOperation"},"customiser_id":{"type":"integer"},"language_code":{"type":"string"},"valid":{"type":"boolean","description":"Always true — a body that would fail returns the update own error response instead of a 200."}},"required":["object","operation","valid"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown or retired key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LanguageTranslationResponse"}}}}},"tags":["Customiser languages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"translations":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"}},"required":["translations"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/legacy-fixed-height-sizes":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List legacy fixed-height sizes\n\nLists the customiser's legacy fixed-height sizes in the order the\nstorefront presents them (`sort_order`, then id). These rows belong to the\ndeprecated fixed-height pricing model: they carry a height and text\nlimits, and nothing else. A customiser on a current pricing model normally\nreports none, and any it does report are leftovers the delete operation\nexists to clear.\n\nThe resource is read- and delete-only. There is no create, update, or\nreorder, so this collection can only ever shrink; a size on a current\npricing model belongs to the sizes resource instead. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.","operationId":"listLegacyFixedHeightSizes","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLegacyFixedHeightSizItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"405":{"description":"Create attempted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser legacy fixed-height sizes"],"security":[{"default":["customisers:read"]}]}},"/api/v3/customisers/{customiser_id}/legacy-fixed-height-sizes/{legacy_size_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"legacy_size_id","description":"The legacy fixed-height size id, from the list operation.","example":"4","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a legacy fixed-height size\n\nReturns one of the customiser's legacy fixed-height sizes. Requires\n`customisers:read` and is safe to retry. These ids come from a different\nsequence than current sizes, so an id from one resource never resolves on\nthe other, and a size belonging to another customiser or another store is\nindistinguishable from an unknown id.","operationId":"getLegacyFixedHeightSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLegacyFixedHeightSizItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"405":{"description":"Update attempted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser legacy fixed-height sizes"],"security":[{"default":["customisers:read"]}]},"delete":{"summary":"Delete a legacy fixed-height size\n\nRemoves the legacy fixed-height size and returns a deletion receipt: a\nsnapshot of exactly what was removed, so the caller can verify and report\nthe change. A size anything still references is refused with a 409\n`resource_in_use` listing the referencing rows, and references are never\nsilently cleared. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThis is the only write the resource offers. Deleting the last one leaves\na fixed-height customiser with no sizes at all, and nothing on this API\ncan put one back, so retire the customiser's pricing model first if it is\nstill selling.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLegacyFixedHeightSize","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["legacy_fixed_height_size_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserLegacyFixedHeightSizItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Size still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser legacy fixed-height sizes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/legacy-fixed-height-sizes/{legacy_size_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"legacy_size_id","description":"The legacy fixed-height size id.","example":"4","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a legacy fixed-height size delete\n\nDry-runs the delete without removing anything: an in-use size returns the\nidentical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteLegacyFixedHeightSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"legacy_fixed_height_size_id":{"type":"integer"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Size still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser legacy fixed-height sizes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/length-pricings":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List length pricings\n\nLists the customiser's length pricings, oldest first. Each carries one\nboundary row per size band: `width_limit_cm`, `height_limit_cm`, and\n`length_limit_cm` are the largest sign the row covers, `base_price` and\n`price_per_letter` are integer minor units of `currency`, and the rest\nare decimal rates. These are the same price lists the customiser pricing\ndocument publishes under `price_lists`; the sheet the sign is cut from is\nnot one of them and stays under the document's `sheet_pricing`. Requires\n`pricing:read`. Follow `links.next` while `pagination.has_more` is true,\nsending each cursor back unchanged. The operation is read-only and always\nsafe to retry.","operationId":"listLengthPricings","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserLengthPricingItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"security":[{"default":["pricing:read"]}]},"post":{"summary":"Create a length pricing\n\nAdds a length pricing to the customiser. Only `name` is required; every\nother field has a documented default (`letter_pricing_mode`\nevery_letter_cost, `letter_pricing_method` fixed_letter,\n`shipping.calculation` physical, `shipping.add_weight_to_product` false,\n`rows` empty).\n\nEvery row needs a `base_price`, and the dimension limits its shipping\ncalculation needs: physical measures the sign, so it needs\n`width_limit_cm` and `height_limit_cm`; volumetric measures the box, so it\nneeds at least one of the three limits and a\n`shipping.volumetric_divisor`. Outside a frame-fit customiser each row\nalso needs the rate its letter pricing method charges by —\n`price_per_cm` for `material_length`, `price_per_letter` for\n`fixed_letter`. A write that would leave a row without them is refused\nrather than creating a price list the storefront cannot resolve, and the\npricing document requires exactly the same of these rows.\n\nThe customiser must price by `advanced_letter` or `frame_fit`: a length\npricing on any other pricing model is unreachable, and the write is\nrefused with a 422 `pricing_model_mismatch`. A frame-fit customiser\nprices every sign from exactly one price list, so a second create on one\nis refused too — update the existing list instead. Sheet pricing is not\npart of this family and cannot be created through it.\n\nRequires `pricing:write` and an `Idempotency-Key`. The pricing document\npublishes the same price lists and its full replace deletes any one the\npayload omits, so pick one surface per workflow rather than interleaving\nthe two.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createLengthPricing","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"letter_pricing_method":{"type":"string"},"shipping":{"$ref":"#/components/schemas/CustomiserLengthPricingItemShipping"},"currency":{"type":"string"},"rows":{"type":"array","items":{"type":"object","properties":{"base_price":{"type":"integer"},"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"length_limit_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableString"},"width_modifier":{"$ref":"#/components/schemas/NullableString"},"height_modifier":{"$ref":"#/components/schemas/NullableString"},"length_modifier":{"$ref":"#/components/schemas/NullableString"},"max_price_multiplier":{"$ref":"#/components/schemas/NullableString"},"price_per_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_letter":{"type":"integer"},"price_per_letter_by_position":{"$ref":"#/components/schemas/NullableString"}}}},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Missing dimension limits","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letter_pricing_mode":{"$ref":"#/components/schemas/CustomiserFontTierLetterPricingMode"},"letter_pricing_method":{"type":"string","description":"What a letter costs are derived from. Defaults to fixed_letter.","example":"fixed_letter","enum":["material_length","fixed_letter"]},"shipping":{"type":"object","description":"How the shipping dimensions of the finished sign are derived.","example":{"calculation":"physical","add_weight_to_product":false},"properties":{"calculation":{"type":"string","description":"How the shipping dimensions are derived. Defaults to physical.","example":"physical","enum":["physical","volumetric"]},"volumetric_divisor":{"type":"integer","description":"The divisor the boxed volume is divided by. Required whenever the calculation is volumetric.","example":5000},"add_weight_to_product":{"type":"boolean","description":"Whether the calculated weight is written onto the created product.","example":false}}},"name":{"type":"string","description":"The price list's display name.","example":"Standard"},"rows":{"type":"array","description":"One boundary row per size band.","example":[{"base_price":4500,"width_limit_cm":120,"height_limit_cm":60,"price_per_letter":600}],"items":{"type":"object","properties":{"base_price":{"type":"integer","description":"The flat amount a sign in this band costs, in integer minor units.","example":4500},"width_limit_cm":{"type":"number","description":"The widest sign this row covers, in centimetres.","example":120},"height_limit_cm":{"type":"number","description":"The tallest sign this row covers, in centimetres.","example":60},"length_limit_cm":{"type":"number","description":"The depth boundary this row covers, in centimetres.","example":30},"price_per_cm":{"type":"number","description":"The amount each centimetre of material adds, in decimal major currency units.","example":1.25},"price_per_letter":{"type":"integer","description":"The amount each letter adds, in integer minor units.","example":600},"price_per_sq_cm":{"type":"number","description":"The amount each square centimetre adds, in decimal major currency units.","example":0.022},"max_price_multiplier":{"type":"number","description":"The ceiling the row's computed price is capped at.","example":3},"fixed_length_cm":{"type":"number","description":"The fixed sign depth in centimetres for non-Lightbox volumetric shipping.","example":10},"width_modifier":{"type":"number","description":"Adjusts the measured width before pricing.","example":1.1},"height_modifier":{"type":"number","description":"Adjusts the measured height before pricing.","example":1.1},"length_modifier":{"type":"number","description":"Lightbox-only extra parcel depth added for packaging, in centimetres.","example":1.1},"price_per_letter_by_position":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["base_price"]}}},"required":["name"]}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/length-pricings/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a length pricing create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `pricing:write`.","operationId":"validateCreateLengthPricing","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/length-pricings/{length_pricing_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"length_pricing_id","description":"The length pricing id, from the list length pricings operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a length pricing\n\nReturns one of the customiser's length pricings. Requires `pricing:read`\nand is safe to retry. A length pricing belonging to another customiser or\nanother store — or a sheet pricing row, which this family does not\ncover — is indistinguishable from an unknown id.","operationId":"getLengthPricing","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserLengthPricingItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown length pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"security":[{"default":["pricing:read"]}]},"patch":{"summary":"Update a length pricing\n\nApplies an RFC 7396 JSON Merge Patch to one length pricing: properties\nomitted from the body are unchanged, and the nested `shipping` object\nmerges member by member. `rows` is an array, so sending it replaces every\nrow wholesale. Read-only and unknown fields are rejected. An empty object\n`{}` is a valid no-op. Requires `pricing:write`.\n\nOne exception to wholesale replacement protects per-position pricing: a\nrow resent without `price_per_letter_by_position` keeps the schedule it\nalready had, because dropping a schedule by forgetting to echo it would\nsilently reprice a sign. Send `price_per_letter_by_position: null` to\nclear one deliberately.\n\nThe dimension limits, the volumetric divisor, and the rate each row owes\nits letter pricing method are judged against the state the patch would\nleave behind, so switching an existing price list to volumetric shipping,\nor to a different `letter_pricing_method`, is refused unless the rows it\nalready has can carry it.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateLengthPricing","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"letter_pricing_method":{"type":"string"},"shipping":{"type":"object","properties":{"calculation":{"type":"string"},"volumetric_divisor":{"type":"integer"},"add_weight_to_product":{"type":"boolean"}}},"currency":{"type":"string"},"rows":{"type":"array","items":{"type":"object","properties":{"base_price":{"type":"integer"},"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"length_limit_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableString"},"width_modifier":{"$ref":"#/components/schemas/NullableString"},"height_modifier":{"$ref":"#/components/schemas/NullableString"},"length_modifier":{"$ref":"#/components/schemas/NullableString"},"max_price_multiplier":{"$ref":"#/components/schemas/NullableString"},"price_per_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_letter":{"$ref":"#/components/schemas/NullableString"},"price_per_letter_by_position":{"$ref":"#/components/schemas/NullableString"}}}},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown length pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Missing volumetric divisor","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The price list's display name.","example":"Standard"},"shipping":{"$ref":"#/components/schemas/CustomiserProductCart"},"rows":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"}}}}}},"security":[{"default":["pricing:write"]}]},"delete":{"summary":"Delete a length pricing\n\nRemoves the length pricing and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA price list anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — the fonts priced by it,\nand the customiser settings that price icons by it — and references are\nnever silently cleared. Requires `pricing:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLengthPricing","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["length_pricing_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"type":"string"},"letter_pricing_method":{"type":"string"},"shipping":{"$ref":"#/components/schemas/CustomiserLengthPricingItemShipping"},"currency":{"type":"string"},"rows":{"type":"array"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"description":"The length pricing exactly as it was immediately before deletion."}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown length pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Length pricing still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/length-pricings/{length_pricing_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"length_pricing_id","description":"The length pricing id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a length pricing update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `pricing:write`.","operationId":"validateUpdateLengthPricing","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LengthPricingValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown length pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/length-pricings/{length_pricing_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"length_pricing_id","description":"The length pricing id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a length pricing delete\n\nDry-runs the delete without removing anything: an in-use price list\nreturns the identical 409 `resource_in_use` problem the delete would\nreturn, and a deletable one returns a 200 verdict. Nothing is written and\nno idempotency key is consumed. The verdict is advisory. Requires\n`pricing:write`.","operationId":"validateDeleteLengthPricing","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LengthPricingValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown length pricing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Length pricing still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser length pricings"],"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List letter-part colour selections\n\nLists the letter type's materialised letter-part colour selections. A\npart the letter type lists in `letter_parts` but that has never been\ncreated is absent here rather than invented, so the list is what exists\nrather than what could exist. Requires `customisers:read`. The operation\nis read-only and always safe to retry.","operationId":"listLetterPartColourSelections","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LetterTypeLetterPartItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a letter-part colour selection\n\nCreates the colour selection for one part of a letter type. `letter_part`\nis the half of the identity the caller chooses, so it goes in the body\nhere and nowhere else — every other operation carries it in the path. A\npart that already has a colour selection is refused with a 422\n`letter_part_taken`; update it instead.\n\nTwo configurations are refused rather than quietly rewritten, which is\nwhat the merchant admin does: `text_enabled` may be set only on the front\nface of a rectangle or cylinder lightbox (422\n`customer_text_not_supported`), and a lightbox border carries no\noptionality (422 `field_not_used_by_letter_part`).\n\nA create stores only the fields it names, so an omitted one takes the\ndefault the letter type's category expects. That also keeps a fresh part\ndeletable: the guarded delete only removes a part whose configuration is\nuntouched. `default_upload_image_url` is read-only until the upload\nsurface ships.\n\nThe letter type must already declare the requested part in `letter_parts`.\nAn enum-valid but undeclared part is refused with a 422\n`letter_part_not_declared`. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache. Option writes also spend one unit of a\nseparate per-key option-write budget.","operationId":"createLetterPartColourSelection","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeLetterPartResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/LetterTypeLetterPartOneOf"}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letter_part":{"$ref":"#/components/schemas/CustomiserExtraYesColourExclusionLetterPart"},"name":{"type":"string","description":"The heading shown above the part's colours.","example":"Face colour"},"description":{"type":"string","description":"A description shown alongside the part.","example":"The colour customers see lit"},"optional":{"type":"boolean","description":"Whether the customer may leave this part unselected; omit it to leave the column unset, which the storefront reads as no.","example":false},"optional_label":{"type":"string","description":"The label for the no-selection choice.","example":null},"optional_default_none":{"type":"boolean","description":"Whether no-selection is the default.","example":false},"lightsource":{"type":"boolean","description":"Whether this part is a light source in the render.","example":false},"custom_image":{"type":"boolean","description":"Whether the customer may upload their own artwork for this part.","example":false},"show_default_upload_image":{"type":"boolean","description":"Whether the default artwork is shown before the customer uploads their own.","example":true},"show_in_text_editor":{"type":"boolean","description":"Whether this part appears in the customer's text editor. Omit it to take the default the category expects.","example":true},"text_enabled":{"type":"boolean","description":"Whether the customer may type text on this part. Lightbox front face only.","example":false}},"required":["letter_part"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter-part colour selection create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateCreateLetterPartColourSelection","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterPartValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/LetterTypeLetterPartOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get a letter-part colour selection\n\nReturns one letter-part colour selection, addressed by its letter type\nand letter part. Requires `customisers:read` and is safe to retry. A part\nwith no colour selection yet, an unpublished letter part, and a letter\ntype belonging to another store are all indistinguishable from an unknown\nresource.","operationId":"getLetterPartColourSelection","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeLetterPartResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a letter-part colour selection\n\nApplies an RFC 7396 JSON Merge Patch to one letter-part colour selection:\nproperties omitted from the body are unchanged, and `null` clears a\nnullable property (`name`, `description`, `optional_label`). Read-only\nand unknown fields are rejected — `letter_part` among them, because the\npath already carries it and a part cannot be renamed. An empty object\n`{}` is a valid no-op. The lightbox text and border rules apply here\nexactly as they do on the create. Requires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry. The update clears the customiser's cached storefront\nconfiguration and runs the advisory language sync, and never regenerates\nShopify products, product images, or the product cache.","operationId":"updateLetterPartColourSelection","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"optional":{"type":"boolean"},"optional_label":{"type":"string"},"optional_default_none":{"type":"boolean"},"lightsource":{"type":"boolean"},"custom_image":{"type":"boolean"},"show_default_upload_image":{"type":"boolean"},"show_in_text_editor":{"type":"boolean"},"text_enabled":{"type":"boolean"},"default_upload_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Customer text not supported","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The heading shown above the part's colours; null clears it.","example":"Face colour"},"description":{"type":"string","description":"A description shown alongside the part; null clears it.","example":null},"optional":{"type":"boolean","description":"Whether the customer may leave this part unselected.","example":true},"optional_label":{"type":"string","description":"The label for the no-selection choice; null clears it.","example":"No face colour"},"optional_default_none":{"type":"boolean","description":"Whether no-selection is the default.","example":false},"lightsource":{"type":"boolean","description":"Whether this part is a light source in the render.","example":true},"custom_image":{"type":"boolean","description":"Whether the customer may upload their own artwork for this part.","example":false},"show_default_upload_image":{"type":"boolean","description":"Whether the default artwork is shown before the customer uploads their own.","example":true},"show_in_text_editor":{"type":"boolean","description":"Whether this part appears in the customer's text editor.","example":true},"text_enabled":{"type":"boolean","description":"Whether the customer may type text on this part. Lightbox front face only.","example":false}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter-part colour selection\n\nRemoves a letter part's colour selection and returns a deletion receipt:\na snapshot of exactly what was removed, so the caller can verify and\nreport the change.\n\nThe delete is guarded harder than an option row's, because it removes a\nwhole selection bucket. A bucket that still holds colours, is shared, or\nhas translated copy is refused with a 409 `resource_in_use` naming what\npoints at it. A part the letter type still lists in `letter_parts`, or one\nwhose configuration or uploaded image has been changed from its defaults,\nis refused with a 422 `letter_part_not_deletable` carrying the `reason`:\ndrop the part from the letter type, or reset what was changed, and retry.\n\nRequires `customisers:write` and an `Idempotency-Key`; retrying the same\nrequest and key replays the original receipt after the bucket is gone.","operationId":"deleteLetterPartColourSelection","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["letter_part_colour_selection_deletion_receipt"]},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/LetterTypeLetterPartItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour selection still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LetterTypeLetterPartResponse2"}}}},"422":{"description":"Letter part still configured","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserActivateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter-part colour selection update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateLetterPartColourSelection","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterPartValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"halo","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter-part colour selection delete\n\nDry-runs the delete without removing anything: a bucket that cannot be\nremoved returns the identical problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteLetterPartColourSelection","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterPartValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour selection still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/LetterTypeLetterPartResponse2"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which of the letter part's images to write (closed set): the picture shown to a shopper who is asked to upload their own artwork for this part.","example":"default_upload_image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a letter part default upload image\n\nTakes the raster image of a staged upload and puts it in the letter\npart's `default_upload_image` slot, returning the letter part with\n`default_upload_image_url` pointing at the new file. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` with purpose\n`option_image`, send the bytes to the presigned target it returns, then\nsend that upload's id here. An upload whose purpose is not the slot's\nanswers 422 `upload_purpose_mismatch`. The upload is single use:\nattaching it a second time answers 409.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for\nit stops resolving.\n\nA letter part that holds this image cannot be deleted while it does; the\ndelete below is what releases it.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachLetterPartColourSelectionImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"name":{"$ref":"#/components/schemas/NullableString"},"description":{"$ref":"#/components/schemas/NullableString"},"optional":{"$ref":"#/components/schemas/NullableString"},"optional_label":{"$ref":"#/components/schemas/NullableString"},"optional_default_none":{"type":"boolean"},"lightsource":{"type":"boolean"},"custom_image":{"type":"boolean"},"show_default_upload_image":{"type":"boolean"},"show_in_text_editor":{"type":"boolean"},"text_enabled":{"type":"boolean"},"default_upload_image_url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter part default upload image\n\nClears the letter part's `default_upload_image` slot and deletes the\nstored file, returning a receipt naming what was removed and the letter\npart as it now stands. Requires `customisers:write` and an\n`Idempotency-Key`. Deleting from a slot that holds no image answers 404.\n\nThis is also how a part that carries the image becomes deletable: a\ndeclared part holding a default upload image refuses its own delete with\n`file_bearing`, and clearing the slot here releases that.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLetterPartColourSelectionImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string","description":"The image slot that was cleared.","enum":["default_upload_image"]},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/LetterTypeLetterPartItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which image to write: default_upload_image.","example":"default_upload_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part default upload image attach\n\nDry-runs the attach: the slot must exist on this letter part, the upload\nmust exist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachLetterPartColourSelectionImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse6"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"image_slot","description":"Which image to remove: default_upload_image.","example":"default_upload_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part default upload image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteLetterPartColourSelectionImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse6"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"get":{"summary":"List a letter part's colours\n\nLists the colours a customer can pick for this letter part, in the order\nthe storefront presents them (`sort_order`, then id). The projection is\nthe customiser colour projection unchanged. Requires `customisers:read`.\nFollow `links.next` while `pagination.has_more` is true. The operation is\nread-only and always safe to retry.","operationId":"listLetterPartColours","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a letter part's colour\n\nAdds a colour to this letter part's colour selection, at the end of its\npresentation order. The body, the defaults, and the renderability rules\nare the customiser colours family's, unchanged: `name` is required, a\n`single` colour needs a `hexcode` and a `multi` or `flow` colour needs\n`multi_colour_hexcodes` (422 `colour_value_required`), and the\n`custom_texture` finish is refused until image upload ships. The letter\npart must already have a colour selection — create one through the\nletter-part routes first. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache. Option writes also spend one unit of a\nseparate per-key option-write budget.","operationId":"createLetterPartColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part colour create\n\nDry-runs the create without persisting anything, returning the same\nproblems the create would return or a 200 verdict when it would succeed.\nNothing is written, no idempotency key is consumed, and no option-write\nbudget is spent. The verdict is advisory. Requires `customisers:write`.","operationId":"validateCreateLetterPartColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateCreateResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"post":{"summary":"Reorder a letter part's colours\n\nSets the presentation order of this letter part's colours.\n`colour_ids` must be the complete current membership of the part's colour\nselection, each id exactly once, in the order to present them; a partial\nor stale list is refused with a 422 naming the missing, unknown, and\nduplicated ids rather than being partially applied. Competing reorders of\nunchanged membership are last-write-wins. Requires `customisers:write` and\nan `Idempotency-Key`.","operationId":"reorderLetterPartColours","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part colour reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderLetterPartColours","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateCreateResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"31","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a letter part's colour\n\nReturns one colour of this letter part's colour selection. Requires\n`customisers:read` and is safe to retry. A colour of the customiser's main\nselection, of another letter part, or of another store is\nindistinguishable from an unknown id.","operationId":"getLetterPartColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a letter part's colour\n\nApplies an RFC 7396 JSON Merge Patch to one of this letter part's\ncolours. The semantics are the customiser colours family's, unchanged:\nomitted properties are unchanged, `null` clears a nullable property,\narrays are replaced wholesale, read-only and unknown fields are rejected,\nand the patched result must still be renderable. Requires\n`customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry. The update clears the customiser's cached storefront\nconfiguration and runs the advisory language sync, and never regenerates\nShopify products, product images, or the product cache.","operationId":"updateLetterPartColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightResponse2"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserBacklightRequest2"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter part's colour\n\nRemoves the colour and returns a deletion receipt: a snapshot of exactly\nwhat was removed. A colour anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — references are never\nsilently cleared. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLetterPartColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["letter_part_colour_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"31","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter part colour update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateUpdateLetterPartColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateDeleteResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"31","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter part colour delete\n\nDry-runs the delete without removing anything: an in-use colour returns\nthe identical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteLetterPartColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColourValidateDeleteResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"3","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part the colour belongs to.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the colour's images to write (closed set): the swatch a shopper picks from, the example photograph beside it, or the texture the custom_texture finish renders from.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a letter part colour image\n\nTakes the raster image of a staged upload and puts it in one of the\ncolour's three image slots, returning the colour with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for `image` and `example_image`, `option_texture_image`\nfor `texture_image` — send the bytes to the presigned target it returns,\nthen send that upload's id here. An upload whose purpose is not the\nslot's answers 422 `upload_purpose_mismatch`. The upload is single use:\nattaching it a second time answers 409, and each slot needs its own\nupload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The previous\nfile is deleted once the new one is committed, so any URL published for\nit stops resolving.\n\nAttaching to `texture_image` is what makes the `custom_texture` finish\navailable: send the image here first, then set `texture` on the colour\nitself. The attach clears the customiser's cached storefront\nconfiguration and runs the advisory language sync. It never regenerates\nShopify products, product images, or the product cache.","operationId":"attachLetterPartColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BacklightImageResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part, colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse2"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter part colour image\n\nClears one of the colour's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the colour as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nDeleting the texture image of a colour whose `texture` is\n`custom_texture` is refused with 422 `texture_image_required`: the finish\nrenders from that image, and the storefront could not draw the colour\nwithout it. Change `texture` on the colour first, then delete the image.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLetterPartColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BacklightImageSlot"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"letter_part":{"type":"string"},"colour_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserBacklightItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/FormValidateUpdateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"3","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part the colour belongs to.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part colour image attach\n\nDry-runs the attach: the slot must exist on this colour, the upload must\nexist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark a\ngood upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachLetterPartColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse7"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter part, colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/letter-parts/{letter_part}/colours/{colour_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"3","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_part","description":"The letter part the colour belongs to.","example":"face","required":true,"schema":{"type":"string"}},{"in":"path","name":"colour_id","description":"The colour id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: `image`, `example_image` or `texture_image`.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter part colour image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, a texture image the `custom_texture` finish still needs answers\nthe identical 422, and anything else returns a 200 verdict. Nothing is\nwritten, nothing is removed, and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteLetterPartColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse7"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/FormValidateUpdateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter parts"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List letter types\n\nLists the customiser's letter types in the order the storefront presents\nthem (`sort_order`, then id). `base_price` is integer minor units of\n`currency`, `letter_parts` is the letter type's own structure, and image\nfields are read-only public URLs. A cut-out metal or stencil customiser\noffers no letter types and always returns an empty collection. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.","operationId":"listLetterTypes","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLetterTypeResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a letter type\n\nAdds a letter type to the customiser, at the end of the current\npresentation order. `name` is required, and the letter type must be\nrenderable: it needs at least one active entry in `letter_parts` (422\n`letter_part_required`), a lightbox letter type needs a `shape` (422\n`shape_required`), and every other category refuses one (422\n`shape_not_supported`). Every other field has a documented default\n(`base_price` 0, `price_multiplier` 1, `price_multiplier_target`\nbase_letter_price). Image fields are read-only until the upload surface\nships, and `sort_order` moves only through the reorder operation — naming\neither is a 422. A cut-out metal or stencil customiser refuses the\noperation with a 422 `letter_types_not_supported`. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nA lightbox letter type's `letter_parts` are normalised on the way in: the\nparts its shape cannot draw are dropped, the face and border are forced\nactive, and the rest are ordered as the shape draws them. Read the\nresponse rather than assuming the list came back verbatim.\n\nCreating a letter type does not create the colour selections for its\nparts. Add each one through the letter-part routes, which is also where a\npart's colours are managed.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createLetterType","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLetterTypeResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Letter types not supported","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shape":{"$ref":"#/components/schemas/CustomiserLetterTypeShape"},"letter_parts":{"$ref":"#/components/schemas/CustomiserLetterTypeLetterParts"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The letter type's display name.","example":"Front lit"},"description":{"type":"string","description":"A description shown alongside the option.","example":"Light through the face"},"base_price":{"type":"integer","description":"A flat amount added when the letter type is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the letter type is chosen.","example":1.2}},"required":["letter_parts","name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter type create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateLetterType","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder letter types\n\nSets the storefront presentation order of the customiser's letter types.\n`letter_type_ids` must be the complete current membership of the family,\neach id exactly once, in the order to present them; a partial or stale\nlist is refused with a 422 naming the missing, unknown, and duplicated\nids rather than being partially applied. Competing reorders of unchanged\nmembership are last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderLetterTypes","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLetterTypeResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter type reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderLetterTypes","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id, from the list letter types operation.","example":"7","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a letter type\n\nReturns one letter type of the customiser. Requires `customisers:read`\nand is safe to retry. A letter type belonging to another customiser or\nanother store is indistinguishable from an unknown id, as is any id on a\ncut-out metal or stencil customiser.","operationId":"getLetterType","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLetterTypeResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a letter type\n\nApplies an RFC 7396 JSON Merge Patch to one letter type: properties\nomitted from the body are unchanged, and `null` clears a nullable\nproperty (`description`, `shape`). `letter_parts` is an array and is\nreplaced wholesale. Read-only and unknown fields are rejected — sending\none at all is a 422, whatever its value. An empty object `{}` is a valid\nno-op. The patched result must still be renderable, so a patch that would\nleave no active letter part, clear a lightbox letter type's shape, or set\na shape on any other category is refused. Requires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.\n\nDropping a part from `letter_parts` also deletes that part's colour\nselection and its colours inside the same transaction. A referenced child\ncolour refuses the whole patch with a 409 rather than leaving a dangling\nreference. Re-adding the part starts without a colour selection until one\nis created again.","operationId":"updateLetterType","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserLetterTypeResponse2"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf2"},{"$ref":"#/components/schemas/CustomiserLetterTypeOneOf"},{"$ref":"#/components/schemas/CustomiserLetterTypeOneOf2"}],"required":["type","title","status","code","detail","request_id"]}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"shape":{"$ref":"#/components/schemas/CustomiserLetterTypeShape"},"letter_parts":{"$ref":"#/components/schemas/CustomiserLetterTypeLetterParts"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The letter type's display name.","example":"Front lit"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"Light through the face"},"base_price":{"type":"integer","description":"A flat amount added when the letter type is chosen, in integer minor units.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the letter type is chosen.","example":1.2}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter type\n\nRemoves the letter type and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA letter type anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — references are never\nsilently cleared. A letter type's own letter-part colour selections count\nas references, so delete each one through the letter-part routes first.\nRequires `customisers:write` and an `Idempotency-Key`; retrying the same\nrequest and key replays the original receipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLetterType","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["letter_type_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserLetterTypeItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Letter type still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse2"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter type update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateLetterType","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserLetterTypeOneOf"},{"$ref":"#/components/schemas/CustomiserLetterTypeOneOf2"}],"required":["type","title","status","code","detail","request_id"]}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a letter type delete\n\nDry-runs the delete without removing anything: a referenced letter type\nreturns the identical 409 `resource_in_use` problem the delete would\nreturn, and a deletable one returns a 200 verdict. Nothing is written and\nno idempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteLetterType","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LetterTypeValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Letter type still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse2"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the letter type's images to write (closed set): the artwork shown for this letter construction.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a letter type image\n\nTakes the raster image of a staged upload and puts it in the letter\ntype's image slot, returning the letter type with that slot's `*_url`\npointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachLetterTypeImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"shape":{"$ref":"#/components/schemas/NullableString"},"letter_parts":{"$ref":"#/components/schemas/CustomiserLetterTypeItemLetterParts"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a letter type image\n\nClears one of the letter type's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the letter type as it\nnow stands. Requires `customisers:write` and an `Idempotency-Key`.\nDeleting from a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteLetterTypeImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/ExtraImageSlot"},"customiser_id":{"type":"integer"},"letter_type_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserLetterTypeItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter type image attach\n\nDry-runs the attach: the slot must exist on this letter type, the upload\nmust exist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachLetterTypeImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse8"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown letter type, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/letter-types/{letter_type_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"letter_type_id","description":"The letter type id.","example":"7","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a letter type image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteLetterTypeImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse8"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser letter types"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List materials\n\nLists the customiser's material options in the order the storefront\npresents them (`sort_order`, then id). `base_price` is integer minor units\nof `currency`; the exclusion arrays name the customiser's own backboards\nand mountings; image fields are read-only public URLs. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.","operationId":"listMaterials","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMaterialResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a material\n\nAdds a material to the customiser, at the end of the current presentation\norder. Only `name` is required; every other field has a documented default\n(both exclusion arrays empty, `base_price` 0, `price_multiplier` 1,\n`price_multiplier_target` base_letter_price).\n\n`excluded_backboard_ids` and `excluded_mounting_ids` must name the\ncustomiser's own backboards and mountings — an id from another customiser\nor another store is a 422 on the offending element. Image fields are\nread-only until the upload surface ships, and `sort_order` moves only\nthrough the reorder operation; naming either is a 422.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createMaterial","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMaterialResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Foreign exclusion id","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The material's display name.","example":"Brushed Aluminium"},"description":{"type":"string","description":"A description shown alongside the option.","example":"3mm brushed aluminium face"},"excluded_backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the material is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the material is chosen.","example":1.2}},"required":["name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a material create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateMaterial","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder materials\n\nSets the storefront presentation order of the customiser's materials.\n`material_ids` must be the complete current membership of the family, each\nid exactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderMaterials","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMaterialResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a material reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderMaterials","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id, from the list materials operation.","example":"5","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a material\n\nReturns one of the customiser's materials. Requires `customisers:read` and\nis safe to retry. A material belonging to another customiser or another\nstore is indistinguishable from an unknown id.","operationId":"getMaterial","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMaterialResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a material\n\nApplies an RFC 7396 JSON Merge Patch to one material: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`). Arrays are replaced wholesale, so sending\n`excluded_backboard_ids` replaces the whole exclusion list. Read-only and\nunknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateMaterial","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"excluded_backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_mounting_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The material's display name.","example":"Brushed Aluminium"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"3mm brushed aluminium face"},"excluded_backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the material is chosen, in integer minor units.","example":500}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a material\n\nRemoves the material and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A\nmaterial anything still references is refused with a 409 `resource_in_use`\nlisting the referencing rows — saved presets that chose it — and\nreferences are never silently cleared. Requires `customisers:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMaterial","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["material_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserMaterialItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Material still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id.","example":"5","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a material update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateMaterial","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id.","example":"5","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a material delete\n\nDry-runs the delete without removing anything: an in-use material returns\nthe identical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteMaterial","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MaterialValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Material still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id.","example":"5","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the material's images to write (closed set): the artwork shown for the material, or the smaller picture on the button that selects it.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a material image\n\nTakes the raster image of a staged upload and puts it in one of the\nmaterial's two image slots, returning the material with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachMaterialImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"excluded_backboard_ids":{"type":"array"},"excluded_mounting_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"type":"string"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a material image\n\nClears one of the material's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the material as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMaterialImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BackboardImageSlot"},"customiser_id":{"type":"integer"},"material_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserMaterialItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id.","example":"5","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a material image attach\n\nDry-runs the attach: the slot must exist on this material, the upload\nmust exist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachMaterialImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse9"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown material, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/materials/{material_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"material_id","description":"The material id.","example":"5","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a material image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteMaterialImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse9"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser materials"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List mounting colours\n\nLists the customiser's mounting colour options in the order the storefront\npresents them (`sort_order`, then id). `mounting_ids` names the mountings\neach colour is offered for, `hexcode` and `opacity` are the two halves of\nthe colour the storefront paints the hardware with, `base_price` is\ninteger minor units of `currency`, and image fields are read-only public\nURLs. Requires `customisers:read`. Follow `links.next` while\n`pagination.has_more` is true, sending each cursor back unchanged. The\noperation is read-only and always safe to retry.","operationId":"listMountingColours","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingColourResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a mounting colour\n\nAdds a mounting colour to the customiser, at the end of the current\npresentation order. `name`, `hexcode`, and `mounting_ids` are required —\na colour with no hex value paints the hardware with nothing, and one no\nmounting offers is unreachable in the storefront. Every other field has a\ndocumented default (`opacity` 1, `applied_to_visualisation` true,\n`min_size_rule` any, `base_price` 0, `price_multiplier` 1,\n`price_multiplier_target` base_letter_price).\n\n`mounting_ids` must name the customiser's own mountings — an id from\nanother customiser or another store is a 422 on the offending element.\nImage fields are read-only until the upload surface ships, and `sort_order`\nmoves only through the reorder operation; naming either is a 422.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createMountingColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"4c5d6e7f-8a9b-4c0d-9e1f-2a3b4c5d6e7f","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingColourResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Colour value missing","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The colour's display name.","example":"Brushed Steel"},"description":{"type":"string","description":"A description shown alongside the option.","example":"A satin brushed stainless finish"},"hexcode":{"type":"string","description":"The colour the storefront paints the hardware with, as a three or six digit hex value.","example":"#b5b5b5"},"opacity":{"type":"number","description":"How opaque the colour is painted, from 0 to 1. Defaults to 1.","example":1},"applied_to_visualisation":{"type":"boolean","description":"Whether the storefront paints the mounting with this colour. Defaults to true.","example":true},"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"type":"number","description":"The narrowest sign this colour is offered on, in centimetres.","example":30},"min_height_cm":{"type":"number","description":"The shortest sign this colour is offered on, in centimetres.","example":10},"base_price":{"type":"integer","description":"A flat amount added when the colour is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the colour is chosen.","example":1.2}},"required":["name","hexcode","mounting_ids"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting colour create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateMountingColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder mounting colours\n\nSets the storefront presentation order of the customiser's mounting\ncolours. `mounting_colour_ids` must be the complete current membership of\nthe family, each id exactly once, in the order to present them; a partial\nor stale list is refused with a 422 naming the missing, unknown, and\nduplicated ids rather than being partially applied. Competing reorders of\nunchanged membership are last-write-wins. Requires `customisers:write` and\nan `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderMountingColours","parameters":[{"in":"header","name":"Idempotency-Key","example":"0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingColourResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting colour reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderMountingColours","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id, from the list mounting colours operation.","example":"14","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a mounting colour\n\nReturns one of the customiser's mounting colours. Requires\n`customisers:read` and is safe to retry. A mounting colour belonging to\nanother customiser or another store is indistinguishable from an unknown\nid, as is one that has been deleted.","operationId":"getMountingColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingColourResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a mounting colour\n\nApplies an RFC 7396 JSON Merge Patch to one mounting colour: properties\nomitted from the body are unchanged, and `null` clears a nullable property\n(`description`, the minimum sizes). Arrays are replaced wholesale, so\nsending `mounting_ids` replaces the whole list. Read-only and unknown\nfields are rejected. An empty object `{}` is a valid no-op. Requires\n`customisers:write`.\n\n`hexcode` and `opacity` are two views of one stored colour, so a patch\nnaming either keeps the other. The colour cannot be cleared and\n`mounting_ids` cannot be emptied: either would leave a colour the\nstorefront never shows.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateMountingColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"hexcode":{"type":"string"},"opacity":{"type":"number"},"applied_to_visualisation":{"type":"boolean"},"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The colour's display name.","example":"Brushed Steel"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"A satin brushed stainless finish"},"hexcode":{"type":"string","description":"The colour the storefront paints the hardware with, as a three or six digit hex value.","example":"#b5b5b5"},"opacity":{"type":"number","description":"How opaque the colour is painted, from 0 to 1.","example":0.8},"applied_to_visualisation":{"type":"boolean","description":"Whether the storefront paints the mounting with this colour.","example":true},"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"type":"number","description":"The narrowest sign this colour is offered on, in centimetres; null clears it.","example":30},"min_height_cm":{"type":"number","description":"The shortest sign this colour is offered on, in centimetres; null clears it.","example":10},"base_price":{"type":"integer","description":"A flat amount added when the colour is chosen, in integer minor units.","example":500}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a mounting colour\n\nRemoves the mounting colour and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nThe colour stops being offered immediately and never comes back on this\nAPI — a later read answers 404 exactly as an unknown id does. A colour\nanything still references is refused with a 409 `resource_in_use` listing\nthe referencing rows — saved presets that chose it — and references are\nnever silently cleared. Requires `customisers:write` and an\n`Idempotency-Key`; retrying the same request and key replays the original\nreceipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMountingColour","parameters":[{"in":"header","name":"Idempotency-Key","example":"9c0d1e2f-3a4b-4c5d-8e6f-7a8b9c0d1e2f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["mounting_colour_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserMountingColourItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Mounting colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id.","example":"14","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting colour update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateMountingColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id.","example":"14","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting colour delete\n\nDry-runs the delete without removing anything: an in-use mounting colour\nreturns the identical 409 `resource_in_use` problem the delete would\nreturn, and a deletable one returns a 200 verdict. Nothing is written and\nno idempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteMountingColour","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingColourValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Mounting colour still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the mounting colour's images to write (closed set): the swatch a shopper picks from, or the example photograph beside it.","example":"preview_image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a mounting colour image\n\nTakes the raster image of a staged upload and puts it in one of the\nmounting colour's two image slots, returning the mounting colour with\nthat slot's `*_url` pointing at the new file. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachMountingColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"hexcode":{"type":"string"},"opacity":{"type":"integer"},"applied_to_visualisation":{"type":"boolean"},"mounting_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"type":"string"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a mounting colour image\n\nClears one of the mounting colour's image slots and deletes the stored\nfile, returning a receipt naming what was removed and the mounting\ncolour as it now stands. Requires `customisers:write` and an\n`Idempotency-Key`. Deleting from a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMountingColourImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string","description":"The image slot that was cleared.","enum":["preview_image","example_image"]},"customiser_id":{"type":"integer"},"mounting_colour_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserMountingColourItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: preview_image or example_image.","example":"preview_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a mounting colour image attach\n\nDry-runs the attach: the slot must exist on this mounting colour, the\nupload must exist for this store and this API key, and its purpose must\nbe the slot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachMountingColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse10"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting colour, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mounting-colours/{mounting_colour_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_colour_id","description":"The mounting colour id.","example":"14","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: preview_image or example_image.","example":"preview_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a mounting colour image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteMountingColourImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse10"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mounting colours"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List mountings\n\nLists the customiser's mounting options in the order the storefront\npresents them (`sort_order`, then id). `visualisation` is how the fixing\nis drawn on the sign, and the `*_pct` fields are the proportions the two\nfloor-standing visualisations are drawn at — null on every mounting drawn\nat its default proportions. `base_price` is integer minor units of\n`currency`, and image fields are read-only public URLs. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.","operationId":"listMountings","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a mounting\n\nAdds a mounting to the customiser, at the end of the current presentation\norder. Only `name` is required; every other field has a documented default\n(`visualisation` none, no letter parts, both exclusion arrays empty,\n`base_price` 0, `price_multiplier` 1, `price_multiplier_target`\nbase_letter_price).\n\nThe `*_pct` proportions belong to one visualisation each, so a body naming\none the mounting's visualisation would never read is a 422 on that field.\n`excluded_colour_ids` and `excluded_backboard_ids` must name the\ncustomiser's own colours and backboards — an id from another customiser or\nanother store is a 422 on the offending element. Image fields are\nread-only until the upload surface ships, and `sort_order` moves only\nthrough the reorder operation; naming either is a 422.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createMounting","parameters":[{"in":"header","name":"Idempotency-Key","example":"3f6c1d2e-7a8b-4c9d-8e0f-1a2b3c4d5e6f","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Proportion the visualisation never reads","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visualisation":{"type":"string","description":"How the storefront draws the fixing. Defaults to none. The prong, pedestal and sign feet visualisations are drawn by the lightbox renderer only.","example":"screw","enum":["none","screw","screw_cap","flag","hanging","key_ring","single_prong","double_prong","pedestal","sign_feet"]},"letter_parts":{"type":"array","description":"The sides of the sign this mounting can be fitted to. Defaults to none, which fits the mounting one way only.","example":["top"],"items":{"type":"string","enum":["top","left","right","bottom","back","face"]}},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The mounting's display name.","example":"Wall Screws"},"description":{"type":"string","description":"A description shown alongside the option.","example":"Four stainless screws and wall plugs"},"pedestal_pole_width_pct":{"type":"number","description":"How wide the pedestal pole is drawn, as a percentage of the sign, from 1 to 100.","example":18},"pedestal_base_depth_pct":{"type":"number","description":"How deep the pedestal base is drawn, as a percentage, from 1 to 400.","example":100},"sign_feet_base_depth_pct":{"type":"number","description":"How deep each foot is drawn, as a percentage, from 1 to 400.","example":180},"sign_feet_post_height_pct":{"type":"number","description":"How tall each post is drawn, as a percentage, from 0 to 100.","example":10},"sign_feet_inset_pct":{"type":"number","description":"How far in from the sign edge each foot sits, as a percentage, from 1 to 45.","example":15},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the mounting is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the mounting is chosen.","example":1.2}},"required":["name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateMounting","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder mountings\n\nSets the storefront presentation order of the customiser's mountings.\n`mounting_ids` must be the complete current membership of the family, each\nid exactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderMountings","parameters":[{"in":"header","name":"Idempotency-Key","example":"2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderMountings","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id, from the list mountings operation.","example":"8","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a mounting\n\nReturns one of the customiser's mountings. Requires `customisers:read` and\nis safe to retry. A mounting belonging to another customiser or another\nstore is indistinguishable from an unknown id.","operationId":"getMounting","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserMountingResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a mounting\n\nApplies an RFC 7396 JSON Merge Patch to one mounting: properties omitted\nfrom the body are unchanged, and `null` clears a nullable property\n(`description`, the proportions). Arrays are replaced wholesale, so\nsending `excluded_colour_ids` replaces the whole exclusion list. Read-only\nand unknown fields are rejected. An empty object `{}` is a valid no-op.\nRequires `customisers:write`.\n\nChanging `visualisation` drops the proportions the old visualisation was\ndrawn from, because the storefront would never read them again; send the\nnew visualisation's proportions in the same body to set them.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateMounting","parameters":[{"in":"header","name":"Idempotency-Key","example":"8d7e6f5a-4b3c-4d2e-9f1a-0b9c8d7e6f5a","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"visualisation":{"type":"string"},"pedestal_pole_width_pct":{"$ref":"#/components/schemas/NullableString"},"pedestal_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_post_height_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_inset_pct":{"$ref":"#/components/schemas/NullableString"},"letter_parts":{"type":"array"},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_backboard_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"$ref":"#/components/schemas/NullableString"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The mounting's display name.","example":"Wall Screws"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"Four stainless screws and wall plugs"},"pedestal_pole_width_pct":{"type":"number","description":"How wide the pedestal pole is drawn, as a percentage of the sign; null clears it.","example":18},"pedestal_base_depth_pct":{"type":"number","description":"How deep the pedestal base is drawn, as a percentage; null clears it.","example":100},"sign_feet_base_depth_pct":{"type":"number","description":"How deep each foot is drawn, as a percentage; null clears it.","example":180},"sign_feet_post_height_pct":{"type":"number","description":"How tall each post is drawn, as a percentage; null clears it.","example":10},"sign_feet_inset_pct":{"type":"number","description":"How far in from the sign edge each foot sits, as a percentage; null clears it.","example":15},"excluded_colour_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"excluded_backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"base_price":{"type":"integer","description":"A flat amount added when the mounting is chosen, in integer minor units.","example":500}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a mounting\n\nRemoves the mounting and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A\nmounting anything live still references is refused with a 409\n`resource_in_use` listing the referencing rows: the mounting colours\noffered for it, the materials that exclude it, and saved presets that\nchose it. Deleted mounting-colour rows are reconciled instead. The delete\nremoves this mounting from a shared tombstone, or permanently removes a\ntombstone that would otherwise be empty. Requires `customisers:write` and\nan `Idempotency-Key`; retrying the same request and key replays the\noriginal receipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMounting","parameters":[{"in":"header","name":"Idempotency-Key","example":"6e5d4c3b-2a19-4f8e-9d7c-6b5a4938271e","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["mounting_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserMountingItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Mounting still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id.","example":"8","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateMounting","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id.","example":"8","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a mounting delete\n\nDry-runs the delete without removing anything: an in-use mounting returns\nthe identical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteMounting","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MountingValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Mounting still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id.","example":"8","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the mounting's images to write (closed set): the artwork shown for the mounting, or the smaller picture on the button that selects it.","example":"image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a mounting image\n\nTakes the raster image of a staged upload and puts it in one of the\nmounting's two image slots, returning the mounting with that slot's\n`*_url` pointing at the new file. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for every slot — send the bytes to the presigned target\nit returns, then send that upload's id here. An upload whose purpose is\nnot the slot's answers 422 `upload_purpose_mismatch`. The upload is\nsingle use: attaching it a second time answers 409, and each slot needs\nits own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachMountingImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"visualisation":{"type":"string"},"pedestal_pole_width_pct":{"$ref":"#/components/schemas/NullableString"},"pedestal_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_base_depth_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_post_height_pct":{"$ref":"#/components/schemas/NullableString"},"sign_feet_inset_pct":{"$ref":"#/components/schemas/NullableString"},"letter_parts":{"type":"array"},"excluded_colour_ids":{"type":"array"},"excluded_backboard_ids":{"type":"array"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"image_url":{"type":"string"},"button_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a mounting image\n\nClears one of the mounting's image slots and deletes the stored file,\nreturning a receipt naming what was removed and the mounting as it now\nstands. Requires `customisers:write` and an `Idempotency-Key`. Deleting\nfrom a slot that holds no image answers 404.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteMountingImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"$ref":"#/components/schemas/BackboardImageSlot"},"customiser_id":{"type":"integer"},"mounting_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserMountingItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id.","example":"8","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a mounting image attach\n\nDry-runs the attach: the slot must exist on this mounting, the upload\nmust exist for this store and this API key, and its purpose must be the\nslot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachMountingImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse11"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown mounting, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/mountings/{mounting_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"mounting_id","description":"The mounting id.","example":"8","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: image or button_image.","example":"image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a mounting image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, and anything else returns a 200 verdict. Nothing is written,\nnothing is removed, and no idempotency key is consumed. The verdict is\nadvisory. Requires `customisers:write`.","operationId":"validateDeleteMountingImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse11"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser mountings"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/presets":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List presets\n\nLists the customiser's presets, oldest first. Each entry is a\n`preset_summary`: everything about the preset except its design document,\nplus that document's size. A design can hold two megabytes, so fetching\none preset is what returns it. Requires `customisers:read`. Follow\n`links.next` while `pagination.has_more` is true, sending each cursor\nback unchanged. The operation is read-only and always safe to retry.","operationId":"listPresets","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"code":{"type":"string"},"design_key_count":{"type":"integer"},"design_byte_size":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a preset\n\nAdds a preset to the customiser and returns it with the `code` the server\ngenerated for it. Both `name` and `design` are required: a preset with\nnothing to restore is not a starting point.\n\n`design` is the snapshot the customer customiser produces when a design\nis saved, and v3 stores it verbatim. Nothing in this API composes one, so\nan integration builds a design in the customiser (or reads it back from\nan existing preset) rather than authoring the document by hand. It must\nbe a non-empty JSON object; a document above the size cap is refused,\nand one above the 2 MiB request-body limit is a 413 before it reaches\nvalidation.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createPreset","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPresetResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Empty design","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPresetRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/presets/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a preset create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreatePreset","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"preset_id":{"$ref":"#/components/schemas/NullableString"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPresetRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/presets/{preset_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"preset_id","description":"The preset id, from the list presets operation.","example":"31","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a preset\n\nReturns one of the customiser's presets, including its design document.\nRequires `customisers:read` and is safe to retry. A preset belonging to\nanother customiser or another store is indistinguishable from an unknown\nid.","operationId":"getPreset","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"code":{"type":"string"},"design":{"$ref":"#/components/schemas/CustomiserPresetDesign"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["design"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown preset","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a preset\n\nApplies an RFC 7396 JSON Merge Patch to one preset: properties omitted\nfrom the body are unchanged. `design` replaces wholesale — it is one\ndocument, not a set of properties to merge into — so sending it replaces\nthe whole snapshot. An empty object `{}` is a valid no-op. Read-only and\nunknown properties are rejected, and `code` never changes. Requires\n`customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updatePreset","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPresetResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown preset","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/FormValidateUpdateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The preset's display name.","example":"Neon script in pink"},"design":{"$ref":"#/components/schemas/CustomiserProductCart"}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a preset\n\nRemoves the preset and returns a deletion receipt: a snapshot of exactly\nwhat was removed, including its design document, so the caller can verify\nand report the change. Nothing else in a customiser points at a preset,\nso a preset is always deletable — the delete still consults the shared\nreference checker, which is what keeps that answer honest if that ever\nchanges. Requires `customisers:write` and an `Idempotency-Key`; retrying\nthe same request and key replays the original receipt after the row is\ngone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deletePreset","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["preset_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserPresetData"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown preset","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse3"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/presets/{preset_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"preset_id","description":"The preset id.","example":"31","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a preset update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdatePreset","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresetValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown preset","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/FormValidateUpdateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/presets/{preset_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"preset_id","description":"The preset id.","example":"31","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a preset delete\n\nDry-runs the delete without removing anything, returning the same problem\nthe delete would return or a 200 verdict when it would succeed. Nothing is\nwritten and no idempotency key is consumed. The verdict is advisory.\nRequires `customisers:write`.","operationId":"validateDeletePreset","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresetValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown preset","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/PresetValidateDeleteResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser presets"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/price-preview":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser to quote against.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Preview a price\n\nQuote what a design would cost in this Store's currency, without creating\nanything. Send the selections a shopper would have made — text, size, font,\nletter type, colours, backboard, and any additional input options — and the\nquote comes back in integer minor units of the Store currency, exactly as\nthe customiser would price the same design. Requires `pricing:read`. The\noperation persists nothing, takes no `Idempotency-Key`, and is safe to\nrepeat.\n\nMeasurements are inputs, never derived. Advanced Letter and Frame Fit\nprices depend on the finished sign's dimensions, and Advanced Letter price\nlists that charge per centimetre also depend on the measured material\nlength of each word. Those numbers come from measuring the chosen font's\nglyph outlines, which happens in the customiser, so this operation takes\nthem as `measurements` rather than guessing them. Omit one the price needs\nand the response is a 422 with code `validation_failed` naming the field.\n\n`measurements.material_lengths_cm` carries one length per priced word, in\nreading order: words are separated by single spaces, and a run of\nwhitespace is not a word. The customiser charges each word separately and\nrounds each charge, so a single combined length would not always reach the\nsame total.\n\nThe text limits depend on the pricing model. Simple Letter uses the\nselected size's line limit, character limit for each line, and minimum\ncharacter count. Advanced Letter uses the customiser's minimum character\ncount. For both Letter models, the minimum is three characters when the\nconfigured value is not positive. Frame Fit uses the customiser's line\nlimit, total character limit, and minimum character count. Frame Fit also\nchecks width, height, and length against the customiser's minimum and\nmaximum size limits. Advanced Letter checks its exclusive width and height\nmaximums, including the custom-size width cap. Simple Letter checks size\nlimits only when an area-priced backboard uses the supplied measurements.\nAn invalid value returns 422 with code `validation_failed`. The error\npoints to `/text` or the applicable member under `/measurements`.\n\nSome designs are refused rather than approximated. A customiser still on\nthe deprecated Fixed Height pricing model answers 422\n`unsupported_pricing_model`; a Lightbox customiser, a size that prices a\nbordered sheet, or a backboard built from layers each carrying its own\nfinish, answers 422 `unsupported_design`. An option id that does\nnot belong to this customiser answers 422 with `unknown_option` and the\nfield's pointer — recover by reading the customiser's option lists\n(`GET /api/v3/customisers/{customiser_id}/colours` and its siblings).\nReplace the key after 401, grant `pricing:read` after 403, check the\ncustomiser id after 404, and retry a 429 after `retry_after` seconds.","operationId":"previewCustomiserPrice","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"pricing_model":{"$ref":"#/components/schemas/CustomiserPricingModel"},"pricing_model_display_name":{"type":"string"},"currency":{"$ref":"#/components/schemas/NullableString"},"amount":{"type":"integer","description":"The quoted price in integer minor units, after any store-wide discount. This is the amount the store would charge."},"subtotal_amount":{"type":"integer","description":"The price in integer minor units before any store-wide discount."},"discount_amount":{"type":"integer","description":"subtotal_amount minus amount, in integer minor units."},"letter_count":{"type":"integer","description":"Characters priced, whitespace excluded — the count the letter charges were applied to."},"line_count":{"type":"integer","description":"Lines carrying at least one visible character. Line-numbered price lists are read in this order."}},"required":["object","customiser_id","pricing_model","pricing_model_display_name","currency","amount","subtotal_amount","discount_amount","letter_count","line_count"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf"},{"$ref":"#/components/schemas/CustomiserOneOf2"}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser pricing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"The sign text. Newlines separate lines; a line with no visible characters is not priced.","example":"OPEN"},"font_id":{"$ref":"#/components/schemas/NullableInteger"},"size_id":{"$ref":"#/components/schemas/NullableInteger"},"letter_type_id":{"$ref":"#/components/schemas/NullableInteger"},"colour_ids":{"type":"array","description":"Every distinct colour applied to the design. A colour is charged once however many letters carry it.","example":[1018],"items":{"$ref":"#/components/schemas/NullableInteger"}},"backboard_id":{"$ref":"#/components/schemas/NullableInteger"},"support_finish_id":{"$ref":"#/components/schemas/NullableInteger"},"jacket_id":{"$ref":"#/components/schemas/NullableInteger"},"mounting_id":{"$ref":"#/components/schemas/NullableInteger"},"mounting_colour_id":{"$ref":"#/components/schemas/NullableInteger"},"backlight_id":{"$ref":"#/components/schemas/NullableInteger"},"material_id":{"$ref":"#/components/schemas/NullableInteger"},"extras":{"type":"array","description":"The additional input options the shopper chose. Listing one is what selects it.","example":[{"extra_id":12,"value":"matte"}],"items":{"type":["object","null"],"properties":{"extra_id":{"type":"integer","description":"The additional input option.","example":12},"value":{"$ref":"#/components/schemas/NullableString"}},"required":["extra_id"]}},"measurements":{"type":["object","null"],"description":"The design's measured geometry. This API never measures glyphs, so any dimension the price depends on is an input.","example":{"width_cm":50,"height_cm":25.4,"material_lengths_cm":[128.4]},"properties":{"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"},"material_lengths_cm":{"type":"array","description":"One measured material length per priced word, in reading order. Required by Advanced Letter price lists that charge per centimetre.","example":[128.4],"items":{"type":"number"}}}}},"required":["text"]}}}},"security":[{"default":["pricing:read"]}]}},"/api/v2/customisers":{"parameters":[],"get":{"summary":"List customisers\n\nGet all customisers for the current organisation.","operationId":"listCustomisersGetAllCustomisersForTheCurrentOrganisation","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"customisers":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserCustomiser"}}}}}}}},"tags":["Customisers"],"deprecated":true}},"/api/v2/customisers/{customiser}":{"parameters":[{"in":"path","name":"customiser","description":"The customiser ID.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a customiser\n\nRetrieve a single customiser.","operationId":"getACustomiserRetrieveASingleCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"customiser":{"$ref":"#/components/schemas/CustomiserCustomiser"}}}}}}},"tags":["Customisers"],"deprecated":true}},"/api/v3/customisers":{"parameters":[],"get":{"summary":"List customisers\n\nList the store's customisers — the configurable sign products shoppers\ndesign on the storefront. Call this to discover what the store sells and\nto find the integer `id` used by every per-customiser operation. Each\nitem reports the customiser's pricing model (`simple_letter`,\n`advanced_letter` or `frame_fit`, with a human display name), its\n`sign_category` and `product_family`, its styling `selection_mode`\n(`single_style` styles the whole sign as one unit; `per_word` lets the\nshopper style each word), and whether it is `active` on the storefront.\nThe operation is read-only, has no side effects, and is always safe to\nretry. Requires the `customisers:read` scope; results are always limited\nto the store the API key belongs to.\n\nResults are paginated: `data` holds up to `limit` customisers in\ncreation order (oldest first, stable across pages). While\n`pagination.has_more` is true, fetch the next page by requesting\n`links.next` — or by passing `pagination.next_cursor` as the `cursor`\nparameter with the same filters. Cursors are opaque and server-owned:\nsend them back unchanged, never construct or edit one. A cursor is only\nvalid for the exact collection, Store, filters, and sort it was issued\nwith; changing any of them (or altering the cursor) yields a 422 problem\nwith code `invalid_cursor` — restart from the first page by dropping it.\nUnsupported filters or parameters are rejected with a 422 problem\nrather than ignored, so a typo can never silently change meaning.\n\nA 401 problem means the key is missing, invalid, or expired. A 403\nproblem with code `insufficient_scope` means the key is valid but lacks\n`customisers:read`; its `required_scopes` and `granted_scopes` members\nsay which scope is missing. A 429 problem means too many requests: wait\n`retry_after` seconds, then retry. Error responses use RFC 9457\nproblem+json with a machine-readable `code`, a documentation link in\n`type`, and a `request_id` to quote when contacting support.","operationId":"listCustomisers","parameters":[{"in":"query","name":"limit","description":"How many customisers to return per page,\nbetween 1 and 100. Defaults to 20.","example":2,"required":false,"schema":{"type":"integer","description":"How many customisers to return per page,\nbetween 1 and 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"The opaque `pagination.next_cursor` value from\nthe previous page. Omit it for the first page, and keep every other\nparameter identical between pages.","example":null,"required":false,"schema":{"type":"string","description":"The opaque `pagination.next_cursor` value from\nthe previous page. Omit it for the first page, and keep every other\nparameter identical between pages.","example":null}},{"in":"query","name":"filter[active]","description":"Only return customisers that are\n(`true`) or are not (`false`) live on the storefront.","example":"true","required":false,"schema":{"type":"string","description":"Only return customisers that are\n(`true`) or are not (`false`) live on the storefront."}},{"in":"query","name":"filter[pricing_model]","description":"Only return customisers using\nthis pricing model. One of `simple_letter`, `advanced_letter` or\n`frame_fit`.","example":"simple_letter","required":false,"schema":{"type":"string","description":"Only return customisers using\nthis pricing model. One of `simple_letter`, `advanced_letter` or\n`frame_fit`."}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"The page of customiser objects, in creation order (oldest first). Each entry has the same shape as the show operation response — see GET /api/v3/customisers/{customiser} for per-field documentation.","items":{"$ref":"#/components/schemas/CustomiserItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPagination"},"links":{"type":"object","properties":{"self":{"type":"string"},"documentation":{"type":"string"},"next":{"type":"string"}}},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}},"required":["data"]}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf2"},{"description":"Unknown filter","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors2"},"request_id":{"type":"string"}}},{"description":"Unsupported filter value","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/AnalyticsSummaryErrors"},"request_id":{"type":"string"}}}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a customiser\n\nCreates a customiser from one of the store's prototypes. A prototype is a\ncomplete starting point — it fixes the sign category, the pricing model,\nthe sizing strategy, the styling mode, and whether the AI sign designer is\nset up — and the create copies it together with a full set of demo\noptions, sizes, fonts, price lists, forms, and labels for you to edit\nthrough the other operations. List the prototypes to see the ids, and pass\none as `prototype`. The only other field is `name`; omit it and the\ncustomiser takes the prototype's own default name.\n\nWhether the new customiser is live is decided here, not asked for:\n`active` comes back true only when the store's plan still has room to put\none more customiser live and includes this pricing model, and false\notherwise. Building a customiser is always allowed, so a store at its\nlimit gets a complete, editable, not-yet-live customiser rather than a\nrefusal. Activate it later with the activate operation once there is room.\nThe styling mode is the prototype's, narrowed to `single_style` when the\nplan does not include per-word styling, so read `selection_mode` back\nrather than assuming it.\n\nRequires the `customisers:write` scope and an `Idempotency-Key` header\n(1-255 visible ASCII characters, e.g. a UUID): repeating the same key with\nthe same body replays the original response (marked\n`Idempotency-Replay: true`) instead of creating a second customiser, and\nthe same key with a different body is a 409 problem with code\n`idempotency_key_conflict`.\n\nA 422 problem with code `validation_failed` and the error code\n`prototype_not_found` means no prototype has that id. Code\n`prototype_not_available` means the store's plan does not include it —\n`unavailable_reasons` says why. Code `pricing_model_deprecated` means the\nprototype is priced by a model that is no longer offered. A 400 problem\nmeans the body is not valid JSON or the `Idempotency-Key` header is\nmissing or malformed. 401/403/429 behave as on the list operation, with\n`customisers:write` as the required scope.","operationId":"createCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"b2c3d4e5-6f70-4a8b-9c0d-1e2f3a4b5c6d","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"description":"Prototype not on the plan","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"unavailable_reasons":{"$ref":"#/components/schemas/OrderProducts"},"request_id":{"type":"string"}}}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a customiser\n\nRetrieve one customiser by its integer `id` (as returned by the list\noperation). The response reports the customiser's pricing model\n(`simple_letter`, `advanced_letter` or `frame_fit`, with a human display\nname), its `sign_category` and `product_family`, its styling\n`selection_mode`, and whether it is `active` on the storefront. The\noperation is read-only, has no side effects, and is always safe to\nretry. Requires the `customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser does\nnot exist on the store your API key belongs to — use the list operation\nto discover valid ids; ids from another store are indistinguishable\nfrom unknown ids. A 401 problem means the key is missing, invalid, or\nexpired. A 403 problem with code `insufficient_scope` means the key is\nvalid but lacks `customisers:read`. A 429 problem means too many\nrequests: wait `retry_after` seconds, then retry. Error responses use\nRFC 9457 problem+json with a machine-readable `code`, a documentation\nlink in `type`, and a `request_id` to quote when contacting support.","operationId":"getCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"customiser\" for this endpoint (closed set).","enum":["customiser"]},"id":{"type":"integer","description":"The customiser id used in every per-customiser operation path."},"name":{"$ref":"#/components/schemas/NullableString"},"active":{"type":"boolean","description":"Whether the customiser is live on the storefront."},"pricing_model":{"$ref":"#/components/schemas/CustomiserPricingModel"},"pricing_model_display_name":{"type":"string","description":"The pricing model's human display name (e.g. \"Simple Letter\"). Display only — parse pricing_model, never this label."},"sign_category":{"type":["string","null"],"description":"The kind of sign the customiser sells, or null when not set. One of: neon, acrylic_channel_letter, metal_channel_letter, metal_cut_out, stencil, lightbox, wood_letter, aluminium_plate, braille_sign, wood_frame. This set is expected to grow — tolerate unknown values.","enum":["neon","acrylic_channel_letter","metal_channel_letter","metal_cut_out","stencil","lightbox","wood_letter","aluminium_plate","braille_sign","wood_frame",null]},"product_family":{"type":["string","null"],"description":"The manufacturing family the sign belongs to, or null when not set. One of: neon, channel_letter, metal_cut_out. This set is expected to grow — tolerate unknown values.","enum":["neon","channel_letter","metal_cut_out",null]},"selection_mode":{"type":"string","description":"How shoppers style the sign text (closed set): single_style applies one style to the whole sign; per_word lets the shopper style each word.","enum":["single_style","per_word"]},"created_at":{"type":"string","description":"When the customiser was created, ISO 8601 UTC."},"updated_at":{"type":"string","description":"When the customiser configuration last changed, ISO 8601 UTC."}},"required":["object","id","name","active","pricing_model","pricing_model_display_name","sign_category","product_family","selection_mode","created_at","updated_at"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a customiser\n\nApplies an RFC 7396 JSON Merge Patch to one customiser's metadata:\nproperties omitted from the body are unchanged and an empty object `{}` is\na valid no-op. Two properties are patchable — `name`, and `selection_mode`,\nwhich decides whether the shopper styles the whole sign as one unit\n(`single_style`) or each word separately (`per_word`). Everything else a\nread publishes is read-only here: `active` moves only through the activate\nand deactivate operations, and the prototype's choices (`pricing_model`,\n`sign_category`, `product_family`) are fixed once the customiser exists.\nNaming one is a 422 with a pointer rather than a silent no-op. The pricing\nmodel in particular is deliberately not changeable: it would strand the\nprice lists and sizes the customiser already holds.\n\n`selection_mode` carries the settings behind it, exactly as the merchant\nadmin does, because those settings are what per-word styling serves.\nSwitching to `per_word` turns multiple fonts and multiple colours on;\nswitching to `single_style` turns both off. A customiser already styling\neach word by font alone is left as it is. Read the settings document back\nif you track those two values. Switching to `per_word` is refused with a\n422 `selection_mode_not_available`\nwhen the sign is cut as one piece (a lightbox, a metal cut-out, or a\nstencil) or the store's plan does not include per-word styling;\n`reason` says which.\n\nA successful update clears the customiser's cached storefront\nconfiguration and resynchronises translated content. It never regenerates\nyour Shopify products, product images, or product cache. An\n`Idempotency-Key` is optional here, as on the option families' update\noperations: sending one makes the update safe to retry. Requires\n`customisers:write`.","operationId":"updateCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"c3d4e5f6-7a8b-4c9d-8e0f-1a2b3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf"},{"description":"Styling mode unavailable","type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"reason":{"type":"string"},"request_id":{"type":"string"}}}],"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserRequest2"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a customiser\n\nDeletes the customiser and returns a receipt: the id, `deleted: true`, and\na `resource` snapshot of exactly what was deleted. The delete is\nrecoverable — the restore operation brings the customiser back with its\noptions, pricing, and translations intact — but nothing else can reach it\nin the meantime: reading it, listing it, and every write against it answer\n404 exactly as an id that never existed does. Keep the receipt: the id in\nit is the only way back.\n\nA customiser that storefront products were created from is refused with a\n409 problem, code `resource_in_use`: `referenced_by` names those products\n(up to 100 of them), `referenced_by_count` is the true total, and\n`order_line_item_count` is how many order lines were sold through them.\nDeleting it would leave those product pages with nothing to render, and\ndeleting the customiser removes neither the products nor the order\nhistory, so v3 refuses rather than stranding them. Remove the products\nfirst, then retry.\n\nRequires `customisers:write` and an `Idempotency-Key`; an exact retry\nreplays the original receipt rather than reporting an unknown customiser.\nThe delete clears the customiser's cached storefront configuration and\nnever regenerates your Shopify products, product images, or product cache.","operationId":"deleteCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"f6a7b8c9-0d1e-4f2a-9b3c-4d5e6f7a8b92","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["customiser_deletion_receipt"]},"id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Products still design against it","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse3"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customiser-prototypes":{"parameters":[],"get":{"summary":"List customiser prototypes\n\nLists every prototype a customiser can be created from, and says which of\nthem this store may use. A prototype is a complete starting point: it fixes\nthe sign category, the pricing model, the sizing strategy, the styling\nmode, and whether the AI sign designer is set up, and the create operation\ncopies all of it along with a full set of demo options, sizes, fonts, and\nlabels to edit. Pass a prototype's `id` as the create operation's\n`prototype`.\n\n`available` is false when the store's plan does not include the prototype;\n`unavailable_reasons` says why, and creating from it is refused with the\nsame reasons. The operation is read-only, has no side effects, and is\nalways safe to retry. Requires the `customisers:read` scope.\n\nA 401 problem means the key is missing, invalid, or expired; a 403 problem\nwith code `insufficient_scope` means the key lacks `customisers:read`; a\n429 problem means too many requests — wait `retry_after` seconds, then\nretry.","operationId":"listCustomiserPrototypes","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"default_customiser_name":{"type":"string"},"sign_category":{"type":"string"},"pricing_model":{"type":"string"},"sizing_strategy":{"$ref":"#/components/schemas/NullableString"},"selection_mode":{"type":"string"},"ai_sign_designer":{"type":"boolean"},"available":{"type":"boolean"},"unavailable_reasons":{"type":"array"}}}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:read"]}]}},"/api/v3/customisers/validate-create":{"parameters":[],"post":{"summary":"Validate a customiser create\n\nDry-runs the create without creating anything: the same body, the same\nprototype checks, and the same problems the create would return, or a 200\nverdict when it would succeed. The verdict carries `will_be_active`, the\nactivation-quota answer the create would apply, so an agent can tell a\nmerchant in advance whether the new customiser goes live. Nothing is\nwritten, no idempotency key is consumed, and no option-write budget is\nspent, so the operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create, and the store's live slots can change\nbetween the two calls. Requires `customisers:write`.","operationId":"validateCreateCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"$ref":"#/components/schemas/NullableString"},"prototype":{"type":"string"},"will_be_active":{"type":"boolean","description":"Whether the created customiser would be live on the storefront, judged against the store's plan and the customisers it already has live."},"valid":{"type":"boolean"}},"required":["will_be_active"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown prototype","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser update\n\nDry-runs the metadata merge patch without persisting anything, returning\nthe same problems the update would return or a 200 verdict when it would\nsucceed. Nothing is written, no caches are touched, no settings are\nchanged, and no idempotency key is consumed. The verdict is advisory.\nRequires `customisers:write`.","operationId":"validateUpdateCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"prototype":{"$ref":"#/components/schemas/NullableString"},"will_be_active":{"type":"boolean"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Styling mode unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserActivateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserRequest2"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser delete\n\nDry-runs the delete without removing anything: a customiser storefront\nproducts were created from returns the identical 409 `resource_in_use`\nproblem the delete would return, and a deletable one returns a 200\nverdict. Nothing is written and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Products still design against it","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse3"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/activate":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list operation.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Activate a customiser\n\nPuts the customiser live on the storefront. Activating one that is already\nlive changes nothing and answers 200 with the same resource, so the\noperation is safe to repeat.\n\nLive customisers are what a plan pays for, so activation is checked\nagainst it. A 422 problem with code `activation_not_allowed` names the\nrefusal in `reason`: `no_live_entitlement` (the store has no active\nsubscription), `pricing_model_not_in_plan` (the plan does not include the\npricing model this customiser charges by), or\n`active_customiser_limit_reached` (the store has already put as many\ncustomisers live as its plan allows — deactivate another, or upgrade).\nThe check runs under a lock on the store, so two concurrent activations\ncannot both take the last slot.\n\nRequires `customisers:write` and an `Idempotency-Key`. The activation\nclears the customiser's cached storefront configuration and resynchronises\ntranslated content; it never regenerates your Shopify products, product\nimages, or product cache. 400/401/403/404/409/429 behave as on the other\nwrites.","operationId":"activateCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Plan has no room","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserActivateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/validate-activate":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser activation\n\nDry-runs the activation without changing anything: a customiser the\nstore's plan has no room for returns the identical 422\n`activation_not_allowed` problem the activation would return, and one that\ncan go live returns a 200 verdict whose `will_be_active` is true. Nothing\nis written and no idempotency key is consumed. The verdict is advisory —\nthe store's live slots can change between the two calls. Requires\n`customisers:write`.","operationId":"validateActivateCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"will_be_active":{"type":"boolean","description":"The live state the operation would leave the customiser in."},"valid":{"type":"boolean"}},"required":["will_be_active"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Plan has no room","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserActivateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/deactivate":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Deactivate a customiser\n\nTakes the customiser off the storefront and frees the live slot it held.\nDeactivating one that is already down changes nothing and answers 200 with\nthe same resource, so the operation is safe to repeat. It is never refused\nby the plan.\n\nOne limitation is worth knowing before you rely on this: deactivating does\nnot remove a customiser that is already embedded in a storefront page.\nShoppers reaching that page can still design on it. Deactivation stops the\ncustomiser being offered as a live product; removing an existing embed is\na change to the storefront theme.\n\nRequires `customisers:write` and an `Idempotency-Key`. The deactivation\nclears the customiser's cached storefront configuration and resynchronises\ntranslated content; it never regenerates your Shopify products, product\nimages, or product cache. Existing products and order history are\nuntouched.","operationId":"deactivateCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"e5f6a7b8-9c0d-4e1f-8a2b-3c4d5e6f7a81","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/validate-deactivate":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser deactivation\n\nDry-runs the deactivation without changing anything, returning a 200\nverdict whose `will_be_active` is false. Nothing is written and no\nidempotency key is consumed. Requires `customisers:write`.","operationId":"validateDeactivateCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"customiser_id":{"type":"integer"},"will_be_active":{"type":"boolean"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/restore":{"parameters":[{"in":"path","name":"customiser_id","description":"The deleted customiser's id, from its deletion receipt.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Restore a customiser\n\nBrings a deleted customiser back, with its options, pricing, translations,\nand live state exactly as they were. This is the one operation that\naddresses a deleted customiser: everywhere else a deleted id is\nindistinguishable from an unknown one, so restore it by the id its\ndeletion receipt carries. Restoring one that is not deleted changes\nnothing and answers 200 with the same resource, so the operation is safe\nto repeat.\n\nA customiser that was live when it was deleted comes back live, without\nrechecking the plan — restoring is putting back what was already counted,\nnot claiming a new live slot.\n\nRequires `customisers:write` and an `Idempotency-Key`. The restore clears\nthe customiser's cached storefront configuration and resynchronises\ntranslated content; it never regenerates your Shopify products, product\nimages, or product cache.","operationId":"restoreCustomiser","parameters":[{"in":"header","name":"Idempotency-Key","example":"a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c03","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/validate-restore":{"parameters":[{"in":"path","name":"customiser_id","description":"The deleted customiser's id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a customiser restore\n\nDry-runs the restore without changing anything, returning a 200 verdict.\nNothing is written and no idempotency key is consumed. Like the restore\nitself, this is one of the two operations that address a deleted\ncustomiser. Requires `customisers:write`.","operationId":"validateRestoreCustomiser","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customisers"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/settings":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get customiser settings\n\nRetrieve the customiser's settings as one canonical document. The\nproperties fall into five groups. Feature switches are booleans that turn\nbehaviour on and off (`custom_sizing`, `multi_colour`, `multi_font`,\n`text_editor_enabled`, `snap_to_grid_enabled`, the `generate_*` vector\nexports, and the rest). Limits are whole numbers (`min_sign_char`,\n`max_sign_char`, `max_sign_lines`, `measurement_decimal`,\n`snap_to_grid_size_px`, `discount_amount` in integer minor units of\n`currency`). Dimensions are centimetres and always carry a `_cm` suffix\n(`min_sign_width_cm`, `initial_sign_height_cm`, `max_sign_length_cm`,\n`custom_letter_height_max_cm`, `icon_min_height_cm`), with\n`mobile_preview_height_percent` the one percentage. Presentation choices\nare closed sets published in product language, never internal spellings —\na stored value outside a published set reads back as null. `custom_css` is\nthe merchant's stylesheet for the storefront customiser, and\n`selection_order` is the ordered list of option steps, each naming its\noption `family`, its `label`, and whether it is `hidden`.\n\nA customiser whose settings have never been saved reads every property\nback as null; the first merge patch creates the row. The operation is\nread-only, has no side effects, and is always safe to retry. Requires the\n`customisers:read` scope.\n\nA 404 problem with code `resource_not_found` means the customiser does not\nexist on the store your API key belongs to. A 401 problem means the key is\nmissing, invalid, or expired; a 403 problem with code\n`insufficient_scope` means the key lacks `customisers:read`; a 429 problem\nmeans too many requests — wait `retry_after` seconds, then retry. Error\nresponses use RFC 9457 problem+json with a machine-readable `code` and a\n`request_id` to quote when contacting support.","operationId":"getCustomiserSettings","responses":{"200":{"description":"Customiser settings","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"customiser_settings\" for this endpoint (closed set).","enum":["customiser_settings"]},"customiser_id":{"type":"integer","description":"The customiser these settings belong to."},"currency":{"$ref":"#/components/schemas/NullableString"},"add_to_cart":{"type":"boolean"},"alignment":{"type":"string"},"allow_unsupported_characters":{"type":"boolean"},"app_mode":{"type":"string"},"auto_process_ai_quotes":{"type":"boolean"},"auto_rotate_lightbox":{"type":"boolean"},"braille_dots_use_face_colour":{"type":"boolean"},"braille_table":{"type":"string"},"cart_action":{"type":"string"},"custom_css":{"$ref":"#/components/schemas/NullableString"},"custom_letter_height_input":{"type":"boolean"},"custom_letter_height_max_cm":{"type":"integer"},"custom_size_input":{"type":"string"},"custom_sizing":{"type":"boolean"},"custom_sizing_max_width_cm":{"type":"integer"},"custom_sizing_title":{"type":"string"},"desktop_option_order":{"type":"string"},"desktop_show_sidebar_price":{"type":"boolean"},"disable_braille_untranslatable_popup":{"type":"boolean"},"disable_size_limit_popup":{"type":"boolean"},"discount_amount":{"type":"integer"},"discount_option":{"type":"string"},"discount_percentage":{"type":"integer"},"drag_enabled":{"type":"boolean"},"etsy_listing_url":{"$ref":"#/components/schemas/NullableString"},"etsy_show_price":{"type":"boolean"},"example_hover":{"type":"boolean"},"fixed_visualiser":{"type":"boolean"},"font_display":{"type":"string"},"font_scale":{"type":"integer"},"generate_ai":{"type":"boolean"},"generate_dxf":{"type":"boolean"},"generate_eps":{"type":"boolean"},"generate_svg":{"type":"boolean"},"glow_effect":{"type":"string"},"glow_switch":{"type":"string"},"icon_font_tier_id":{"type":"integer"},"icon_length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"icon_min_height_cm":{"type":"integer"},"initial_sign_height_cm":{"type":"integer"},"initial_sign_length_cm":{"type":"integer"},"initial_sign_width_cm":{"type":"integer"},"letter_height_display":{"type":"boolean"},"local_cache_enabled":{"type":"boolean"},"manually_clear_loading":{"type":"boolean"},"max_sign_char":{"type":"integer"},"max_sign_height_cm":{"type":"integer"},"max_sign_length_cm":{"type":"integer"},"max_sign_lines":{"type":"integer"},"max_sign_width_cm":{"type":"integer"},"measurement_decimal":{"type":"integer"},"measurement_display":{"type":"string"},"measurement_unit":{"type":"string"},"min_sign_char":{"type":"integer"},"min_sign_height_cm":{"type":"integer"},"min_sign_length_cm":{"type":"integer"},"min_sign_width_cm":{"type":"integer"},"mobile_preview_height_percent":{"type":"integer"},"mobile_view":{"type":"string"},"mounting_colour_location":{"type":"string"},"multi_colour":{"type":"boolean"},"multi_font":{"type":"boolean"},"part_measurement_display":{"type":"string"},"prevent_size_up":{"type":"boolean"},"pricebox_position":{"type":"string"},"recessed_borders":{"type":"boolean"},"rotate_enabled":{"type":"boolean"},"scale_enabled":{"type":"boolean"},"scale_text_with_size":{"type":"boolean"},"scrollable_mobile_options":{"type":"boolean"},"selection_order":{"type":["array","null"],"description":"The option steps in the order the customer customiser presents them. Each entry carries family (the option family it presents, in the same product language the option resources use: backboard, support_finish, letter_type, letter_type_colour, colour, backlight, size, font, font_tier, length_pricing, material, mounting, mounting_colour, jacket, extra, icon, text, or the shopper artwork step file), label (the step's heading), and hidden. Null when no order has been saved. A merge patch replaces the whole list.","items":{"$ref":"#/components/schemas/CustomiserSettingSelectionOrder"}},"share_location":{"type":"string"},"show_currency":{"type":"string"},"show_custom_size_button":{"type":"boolean"},"show_day_switch":{"type":"boolean"},"show_glow_switch":{"type":"boolean"},"show_initial_mobile_screen":{"type":"boolean"},"show_menu_steps":{"type":"boolean"},"show_menu_steps_first":{"type":"boolean"},"show_selection_numbers":{"type":"boolean"},"size_display":{"type":"string"},"snap_to_grid_enabled":{"type":"boolean"},"snap_to_grid_size_px":{"type":"integer"},"support_colour_location":{"type":"string"},"taxable_product":{"type":"boolean"},"text_editor_enabled":{"type":"boolean"},"text_orientation_column_direction":{"type":"string"},"text_orientation_default":{"type":"string"},"text_orientation_mode":{"type":"string"},"text_price_start":{"type":"boolean"},"ui_controls_location":{"type":"string"},"zoom_controls_display":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/NullableString"}},"required":["object","customiser_id","currency","custom_css","selection_order","updated_at"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser settings"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update customiser settings\n\nPartially update the customiser's settings with a JSON Merge Patch\n(RFC 7396) of the document the read operation returns: the request body is\na JSON object containing only the properties to change. Properties omitted\nfrom the patch are unchanged, a null value clears a property that can hold\nnull, `selection_order` replaces wholesale (send every step to reorder\none), and an empty object `{}` is a valid no-op. Every property the read\npublishes is patchable except the read-only descriptors (`object`,\n`customiser_id`, `currency`, `updated_at`), and a property this resource\ndoes not define is refused with a JSON Pointer rather than ignored, so a\ntypo can never silently change meaning. Requires the `customisers:write`\nscope.\n\nA successful update immediately changes the storefront: the customiser's\ncached configuration is invalidated and translated language content is\nresynchronised. It never regenerates your Shopify products, product\nimages, or product cache. Send an `Idempotency-Key` header (1-255 visible\nASCII characters, e.g. a UUID) to make the write safe to retry: repeating\nthe same key with the same body replays the original response (marked\n`Idempotency-Replay: true`) without executing again; the same key with a\ndifferent body is a 409 problem with code `idempotency_key_conflict`. The\nheader is optional here, as on the option families' update operations.\n\nValidation failures are 422 problems: `validation_failed` lists every\nfield error in `errors[]` with a JSON `pointer` and, for closed-set\nviolations, the `allowed_values`; `unknown_field` means the body names\nproperties outside the document. The sign minimum, initial, and maximum\ndimensions are checked against the document the patch would leave behind,\nso clearing one end of a range is refused exactly as an impossible value\nis. `icon_font_tier_id` and `icon_length_pricing_id` must name records of\nthis customiser. A 400 problem means the body is not valid JSON or the\n`Idempotency-Key` header is malformed. 401/403/404/429 behave as on the\nread operation, with `customisers:write` as the required scope.","operationId":"updateCustomiserSettings","parameters":[{"in":"header","name":"Idempotency-Key","example":"d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70","schema":{"type":"string"}}],"responses":{"200":{"description":"Patched settings","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"customiser_id":{"type":"integer"},"currency":{"type":"string"},"add_to_cart":{"type":"boolean"},"alignment":{"type":"string"},"allow_unsupported_characters":{"type":"boolean"},"app_mode":{"type":"string"},"auto_process_ai_quotes":{"type":"boolean"},"auto_rotate_lightbox":{"type":"boolean"},"braille_dots_use_face_colour":{"type":"boolean"},"braille_table":{"type":"string"},"cart_action":{"type":"string"},"custom_css":{"type":"string"},"custom_letter_height_input":{"type":"boolean"},"custom_letter_height_max_cm":{"type":"integer"},"custom_size_input":{"type":"string"},"custom_sizing":{"type":"boolean"},"custom_sizing_max_width_cm":{"type":"integer"},"custom_sizing_title":{"type":"string"},"desktop_option_order":{"type":"string"},"desktop_show_sidebar_price":{"type":"boolean"},"disable_braille_untranslatable_popup":{"type":"boolean"},"disable_size_limit_popup":{"type":"boolean"},"discount_amount":{"type":"integer"},"discount_option":{"type":"string"},"discount_percentage":{"type":"integer"},"drag_enabled":{"type":"boolean"},"etsy_listing_url":{"$ref":"#/components/schemas/NullableString"},"etsy_show_price":{"type":"boolean"},"example_hover":{"type":"boolean"},"fixed_visualiser":{"type":"boolean"},"font_display":{"type":"string"},"font_scale":{"type":"integer"},"generate_ai":{"type":"boolean"},"generate_dxf":{"type":"boolean"},"generate_eps":{"type":"boolean"},"generate_svg":{"type":"boolean"},"glow_effect":{"type":"string"},"glow_switch":{"type":"string"},"icon_font_tier_id":{"type":"integer"},"icon_length_pricing_id":{"$ref":"#/components/schemas/NullableString"},"icon_min_height_cm":{"type":"integer"},"initial_sign_height_cm":{"type":"integer"},"initial_sign_length_cm":{"type":"integer"},"initial_sign_width_cm":{"type":"integer"},"letter_height_display":{"type":"boolean"},"local_cache_enabled":{"type":"boolean"},"manually_clear_loading":{"type":"boolean"},"max_sign_char":{"type":"integer"},"max_sign_height_cm":{"type":"integer"},"max_sign_length_cm":{"type":"integer"},"max_sign_lines":{"type":"integer"},"max_sign_width_cm":{"type":"integer"},"measurement_decimal":{"type":"integer"},"measurement_display":{"type":"string"},"measurement_unit":{"type":"string"},"min_sign_char":{"type":"integer"},"min_sign_height_cm":{"type":"integer"},"min_sign_length_cm":{"type":"integer"},"min_sign_width_cm":{"type":"integer"},"mobile_preview_height_percent":{"type":"integer"},"mobile_view":{"type":"string"},"mounting_colour_location":{"type":"string"},"multi_colour":{"type":"boolean"},"multi_font":{"type":"boolean"},"part_measurement_display":{"type":"string"},"prevent_size_up":{"type":"boolean"},"pricebox_position":{"type":"string"},"recessed_borders":{"type":"boolean"},"rotate_enabled":{"type":"boolean"},"scale_enabled":{"type":"boolean"},"scale_text_with_size":{"type":"boolean"},"scrollable_mobile_options":{"type":"boolean"},"selection_order":{"type":"array","items":{"$ref":"#/components/schemas/CustomiserSettingSelectionOrder"}},"share_location":{"type":"string"},"show_currency":{"type":"string"},"show_custom_size_button":{"type":"boolean"},"show_day_switch":{"type":"boolean"},"show_glow_switch":{"type":"boolean"},"show_initial_mobile_screen":{"type":"boolean"},"show_menu_steps":{"type":"boolean"},"show_menu_steps_first":{"type":"boolean"},"show_selection_numbers":{"type":"boolean"},"size_display":{"type":"string"},"snap_to_grid_enabled":{"type":"boolean"},"snap_to_grid_size_px":{"type":"integer"},"support_colour_location":{"type":"string"},"taxable_product":{"type":"boolean"},"text_editor_enabled":{"type":"boolean"},"text_orientation_column_direction":{"type":"string"},"text_orientation_default":{"type":"string"},"text_orientation_mode":{"type":"string"},"text_price_start":{"type":"boolean"},"ui_controls_location":{"type":"string"},"zoom_controls_display":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency key conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser settings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"custom_sizing":{"type":"boolean","description":"Whether shoppers may enter their own size.","example":true},"multi_colour":{"type":"boolean","description":"Whether a sign may use more than one colour.","example":true},"text_editor_enabled":{"type":"boolean","description":"Whether the rich text editor is offered.","example":true},"min_sign_width_cm":{"type":"number","description":"The narrowest sign a shopper may configure, in centimetres.","example":20},"max_sign_width_cm":{"type":"number","description":"The widest sign a shopper may configure, in centimetres.","example":200},"max_sign_char":{"type":"integer","description":"The most characters a sign may carry.","example":40},"discount_amount":{"type":"integer","description":"A fixed discount in integer minor units of the store currency.","example":500},"custom_css":{"type":"string","description":"The stylesheet injected into the storefront customiser. Null clears it.","example":".sc-price { font-weight: 600; }"},"selection_order":{"type":"array","description":"The complete ordered list of option steps. Replaces the saved list wholesale; null clears it.","example":[[]],"items":{"type":"object","properties":{"family":{"type":"string","description":"Which option family the step presents.","example":"colour"},"label":{"type":"string","description":"The step's heading.","example":"Colour"},"hidden":{"type":"boolean","description":"Whether the step is hidden from shoppers.","example":false}},"required":["family","label"]}}}}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/settings/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate customiser settings\n\nDry-run a settings merge patch without persisting anything. The request\nbody is the same merge patch the update operation accepts, and the\nresponse is exactly what that update would produce on the failing paths:\nthe identical 422 problem (`validation_failed` with pointers and\n`allowed_values`, or `unknown_field`) — the same validation code path\nruns, only persistence is skipped. On a patch that would apply, the\nresponse is a 200 `customiser_settings_validation` confirmation. Nothing\nis written, no caches are touched, no idempotency key is consumed, and no\nactivity is recorded, so the operation is always safe to repeat. The\nverdict is advisory: nothing binds it to a later update, and the settings\ncan legitimately change between the two calls. Requires the\n`customisers:write` scope (it is the write's dry-run twin).\n\nA 400 problem means the body is not valid JSON; a 422 problem with code\n`invalid_document` means the body is not a JSON object. 401/403/404/429\nbehave as on the read operation, with `customisers:write` as the required\nscope.","operationId":"validateUpdateCustomiserSettings","responses":{"200":{"description":"Patch would apply","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The verdict type discriminator (closed set).","enum":["customiser_settings_validation"]},"operation":{"$ref":"#/components/schemas/LabelValidateUpdateOperation"},"customiser_id":{"type":"integer"},"valid":{"type":"boolean","description":"Always true — a patch that would fail returns the update's own error response instead of a 200."}},"required":["object","operation","valid"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser settings"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"custom_sizing":{"type":"boolean","description":"Any subset of the settings document — the same body the update operation takes.","example":true}}}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List sizes\n\nLists the customiser's sizes in the order the storefront presents them\n(`sort_order`, then id). Every length is in centimetres, and the field\nnames match the sizes embedded in the pricing document, because they are\nthe same rows. Fields the customiser's pricing model never reads are\npublished as `null` — an advanced-letter customiser prices by\n`width_multiplier` and reports no centimetre dimensions, a frame-fit one\nprices by `width_cm` and `height_cm` and reports no multiplier. Requires\n`customisers:read`. Follow `links.next` while `pagination.has_more` is\ntrue, sending each cursor back unchanged. The operation is read-only and\nalways safe to retry.\n\nA customiser still on the deprecated fixed-height pricing model keeps its\ncurrent sizes in the legacy fixed-height sizes resource instead; this\ncollection then reports only whatever leftover rows the customiser\ncarries, which the delete operation exists to clear.","operationId":"listSizes","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSizResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a size\n\nAdds a size to the customiser, at the end of the current presentation\norder. Only `name` is required on a simple-letter customiser; the other\nmodels add what they price by — `width_multiplier` on advanced letter,\n`width_cm` and `height_cm` on frame fit. A field the customiser's pricing\nmodel never reads is refused with a 422 pointer rather than stored where\nnothing would read it, and `sort_order` moves only through the reorder\noperation.\n\nOn a customiser still using the deprecated fixed-height pricing model the\ncreate is refused: its current sizes live in the legacy fixed-height sizes\nresource, so a row created here would never be offered.\n\nSizes are dual-surfaced. A full replace of the pricing document stays\nauthoritative over this family and deletes any size its payload omits, so\na size created here disappears if the next pricing write does not carry\nit. Read the pricing document first and echo the sizes it reports.\n\nRequires `customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and runs\nthe advisory language sync. It never regenerates Shopify products, product\nimages, or the product cache, so live listings are untouched. Option\nwrites also spend one unit of a separate per-key option-write budget;\nexhausting it returns a 429 with `retry_after`.","operationId":"createSize","parameters":[{"in":"header","name":"Idempotency-Key","example":"5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"line_limit":{"$ref":"#/components/schemas/NullableString"},"min_char":{"$ref":"#/components/schemas/NullableString"},"width_cm":{"type":"integer"},"height_cm":{"$ref":"#/components/schemas/NullableString"},"line_height_cm":{"$ref":"#/components/schemas/NullableString"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"width_multiplier":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"type":"string"},"height_measurement_mode":{"type":"string"},"border_margin_cm":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Field the pricing model does not read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sizing_strategy":{"type":"string","description":"Which dimension the customer fixes, leaving the other to scale. Defaults to fixed_width. Only read by simple-letter pricing.","example":"fixed_width","enum":["fixed_width","fixed_height"]},"height_measurement_mode":{"type":"string","description":"Whether the size value is the full line height or the letter height. Defaults to line_height. Only read by simple-letter pricing.","example":"line_height","enum":["line_height","letter_height"]},"name":{"type":"string","description":"The size's display name.","example":"Small"},"description":{"type":"string","description":"A description shown alongside the option.","example":"Fits a shop window"},"char_limit":{"type":"integer","description":"The most characters a customer may enter at this size.","example":12},"line_limit":{"type":"integer","description":"The most lines a customer may enter at this size, from 1 to 10.","example":1},"min_char":{"type":"integer","description":"The fewest characters a customer may enter at this size.","example":1},"width_cm":{"type":"number","description":"The sign's width in centimetres.","example":40},"height_cm":{"type":"number","description":"The sign's height in centimetres.","example":20},"line_height_cm":{"type":"number","description":"The height of one line of text in centimetres.","example":10},"length_cm":{"type":"number","description":"The sign's depth in centimetres.","example":120},"width_multiplier":{"type":"number","description":"How much material this size consumes relative to the base size.","example":1.5},"border_margin_cm":{"type":"number","description":"The margin kept clear around the sign's border in centimetres, from 0 to 100.","example":5.08}},"required":["name"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a size create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory — nothing\nbinds it to a later create. Requires `customisers:write`.","operationId":"validateCreateSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateCreateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder sizes\n\nSets the storefront presentation order of the customiser's sizes.\n`size_ids` must be the complete current membership of the family, each id\nexactly once, in the order to present them; a partial or stale list is\nrefused with a 422 naming the missing, unknown, and duplicated ids rather\nthan being partially applied. Competing reorders of unchanged membership\nare last-write-wins. Requires `customisers:write` and an\n`Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderSizes","parameters":[{"in":"header","name":"Idempotency-Key","example":"c7d8e9f0-1a2b-4c3d-9e4f-5a6b7c8d9e0f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSizResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a size reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderSizes","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/{size_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"size_id","description":"The size id, from the list sizes operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a size\n\nReturns one of the customiser's sizes. Requires `customisers:read` and is\nsafe to retry. A size belonging to another customiser or another store is\nindistinguishable from an unknown id, and legacy fixed-height sizes are a\nseparate resource with their own id sequence — an id from one never\nresolves on the other.","operationId":"getSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomiserSizItem"},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a size\n\nApplies an RFC 7396 JSON Merge Patch to one size: properties omitted from\nthe body are unchanged, and `null` clears a nullable property. Read-only\nand unknown fields are rejected, as is any field the customiser's pricing\nmodel does not read. An empty object `{}` is a valid no-op. Requires\n`customisers:write`.\n\nThe patch is judged on the state it would leave behind, so clearing a\ndimension the pricing model prices by is refused even when the body only\nnames that one field.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key with\na different body is a 409). The update clears the customiser's cached\nstorefront configuration and runs the advisory language sync, and never\nregenerates Shopify products, product images, or the product cache.","operationId":"updateSize","parameters":[{"in":"header","name":"Idempotency-Key","example":"9f2c3d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"char_limit":{"type":"integer"},"line_limit":{"type":"integer"},"min_char":{"type":"integer"},"width_cm":{"type":"integer"},"height_cm":{"$ref":"#/components/schemas/NullableString"},"line_height_cm":{"type":"integer"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"width_multiplier":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"type":"string"},"height_measurement_mode":{"type":"string"},"border_margin_cm":{"type":"number"},"sort_order":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The size's display name.","example":"Small"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"Fits a shop window"},"char_limit":{"type":"integer","description":"The most characters a customer may enter at this size; null clears it.","example":12},"line_limit":{"type":"integer","description":"The most lines a customer may enter at this size, from 1 to 10; null clears it.","example":1},"min_char":{"type":"integer","description":"The fewest characters a customer may enter at this size; null clears it.","example":1},"width_cm":{"type":"number","description":"The sign's width in centimetres.","example":40},"height_cm":{"type":"number","description":"The sign's height in centimetres.","example":20},"line_height_cm":{"type":"number","description":"The height of one line of text in centimetres.","example":10},"length_cm":{"type":"number","description":"The sign's depth in centimetres.","example":120},"width_multiplier":{"type":"number","description":"How much material this size consumes relative to the base size.","example":1.5},"border_margin_cm":{"type":"number","description":"The margin kept clear around the sign's border in centimetres; null clears it.","example":5.08}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a size\n\nRemoves the size and returns a deletion receipt: a snapshot of exactly\nwhat was removed, so the caller can verify and report the change. A size\nanything still references is refused with a 409 `resource_in_use` listing\nthe referencing rows — fonts tied to it, and saved presets that chose it —\nand references are never silently cleared. Requires `customisers:write`\nand an `Idempotency-Key`; retrying the same request and key replays the\noriginal receipt after the row is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteSize","parameters":[{"in":"header","name":"Idempotency-Key","example":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["size_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserSizItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Size still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/{size_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"size_id","description":"The size id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a size update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/sizes/{size_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"size_id","description":"The size id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a size delete\n\nDry-runs the delete without removing anything: an in-use size returns the\nidentical 409 `resource_in_use` problem the delete would return, and a\ndeletable one returns a 200 verdict. Nothing is written and no idempotency\nkey is consumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteSize","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Size still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser sizes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"List support finishes\n\nLists the customiser's support finish options — the colours and textures\nits backboards can be finished in — in the order the storefront presents\nthem (`sort_order`, then id). `base_price` is integer minor units of\n`currency`; minimum sizes are centimetres; `opacity` runs from 0 to 1;\nimage fields are read-only public URLs. Requires `customisers:read`.\nFollow `links.next` while `pagination.has_more` is true, sending each\ncursor back unchanged. The operation is read-only and always safe to\nretry.","operationId":"listSupportFinishes","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSupportFinishResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:read"]}]},"post":{"summary":"Create a support finish\n\nAdds a support finish to the customiser, at the end of the current\npresentation order. `name` and `backboard_ids` are required —\n`backboard_ids` because a finish no backboard offers is unreachable in\nthe storefront, and every id must be one of this customiser's backboards.\nEvery other field has a documented default (`finish_kind` colour,\n`opacity` 1, `applied_to_visualisation` true, `min_size_rule` any,\n`base_price` 0, `price_multiplier` 1, `price_multiplier_target`\nbase_letter_price). Image fields are read-only until the upload surface\nships, `layers` is not writable, and `sort_order` moves only through the\nreorder operation — naming any of them is a 422. A finish must be\nrenderable: a `colour` finish needs a `hexcode` (422\n`colour_value_required`), and the `texture` kind is refused with a 422\n`texture_image_required` until image upload ships. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nThe write clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache, so live listings are untouched.\nOption writes also spend one unit of a separate per-key option-write\nbudget; exhausting it returns a 429 with `retry_after`.","operationId":"createSupportFinish","parameters":[{"in":"header","name":"Idempotency-Key","example":"6d8f0a2c-4e5b-4d9e-8f3a-5b7c9d1e3f4a","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSupportFinishResponse2"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"finish_kind":{"$ref":"#/components/schemas/CustomiserSupportFinishFinishKind"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The finish's display name.","example":"Gloss Black"},"description":{"type":"string","description":"A description shown alongside the option.","example":"A deep gloss black"},"hexcode":{"type":"string","description":"The colour the backboard is finished in, as a hex value. Required in practice for a colour finish.","example":"#111111"},"opacity":{"type":"number","description":"How opaque the finish renders, from 0 to 1. Defaults to 1.","example":1},"applied_to_visualisation":{"type":"boolean","description":"Whether the storefront paints the backboard with this finish. Defaults to true.","example":true},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"type":"number","description":"Minimum sign width, in centimetres, before the finish is offered.","example":null},"min_height_cm":{"type":"number","description":"Minimum sign height, in centimetres, before the finish is offered.","example":null},"base_price":{"type":"integer","description":"A flat amount added when the finish is chosen, in integer minor units of the store currency.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the finish is chosen.","example":1.2}},"required":["name","backboard_ids"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/validate-create":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a support finish create\n\nDry-runs the create without persisting anything: the same body, the same\nvalidation, and the same problems the create would return, or a 200\nverdict when it would succeed. Nothing is written, no caches are touched,\nno idempotency key is consumed, and no option-write budget is spent, so\nthe operation is always safe to repeat. The verdict is advisory —\nnothing binds it to a later create. Requires `customisers:write`.","operationId":"validateCreateSupportFinish","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The finish's display name — the same body the create accepts.","example":"Gloss Black"},"hexcode":{"type":"string","description":"The colour the backboard is finished in, as a hex value.","example":"#111111"},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"}},"required":["name","backboard_ids"]}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Reorder support finishes\n\nSets the storefront presentation order of the customiser's support\nfinishes. `support_finish_ids` must be the complete current membership of\nthe family, each id exactly once, in the order to present them; a partial\nor stale list is refused with a 422 naming the missing, unknown, and\nduplicated ids rather than being partially applied. Competing reorders of\nunchanged membership are last-write-wins. Requires `customisers:write`\nand an `Idempotency-Key`.\n\nThe reorder clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"reorderSupportFinishes","parameters":[{"in":"header","name":"Idempotency-Key","example":"9a1b3d5f-7c8e-4a2b-9c6d-8e0f2a4b6c7d","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSupportFinishResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/validate-reorder":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a support finish reorder\n\nDry-runs the reorder without changing anything: membership drift returns\nthe identical 422 the reorder would return, and a complete membership\nreturns a 200 verdict. Nothing is written and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateReorderSupportFinishes","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishValidateCreateResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Membership drift","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardReorderResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishReorderRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id, from the list support finishes operation.","example":"27","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a support finish\n\nReturns one support finish of the customiser. Requires `customisers:read`\nand is safe to retry. A finish belonging to another customiser or another\nstore is indistinguishable from an unknown id.","operationId":"getSupportFinish","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserSupportFinishResponse2"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:read"]}]},"patch":{"summary":"Update a support finish\n\nApplies an RFC 7396 JSON Merge Patch to one support finish: properties\nomitted from the body are unchanged, and `null` clears a nullable\nproperty (`description`, `hexcode`, `min_width_cm`, `min_height_cm`).\nArrays are replaced wholesale, so `backboard_ids` replaces the whole\nassignment and must still name at least one of this customiser's\nbackboards. Read-only and unknown fields are rejected — sending one at\nall is a 422, whatever its value. An empty object `{}` is a valid no-op.\n\n`hexcode` and `opacity` are two halves of one stored colour: naming\neither leaves the other as it was. The patched result must still be\nrenderable, so clearing the `hexcode` of a `colour` finish is a 422\n`colour_value_required`, and switching to the `texture` kind is refused\nwith a 422 `texture_image_required` unless the finish already carries a\ntexture image. Requires `customisers:write`.\n\nAn `Idempotency-Key` is optional here: sending one makes the update safe\nto retry (an exact repeat replays the original response; the same key\nwith a different body is a 409). The update clears the customiser's\ncached storefront configuration and runs the advisory language sync, and\nnever regenerates Shopify products, product images, or the product cache.","operationId":"updateSupportFinish","parameters":[{"in":"header","name":"Idempotency-Key","example":"7e9f1b3d-5a6c-4e0f-9a4b-6c8d0e2f4a5b","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"finish_kind":{"type":"string"},"hexcode":{"type":"string"},"opacity":{"type":"number"},"applied_to_visualisation":{"type":"boolean"},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"texture_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Read-only field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"finish_kind":{"$ref":"#/components/schemas/CustomiserSupportFinishFinishKind"},"min_size_rule":{"$ref":"#/components/schemas/CustomiserBacklightMinSizeRule"},"price_multiplier_target":{"$ref":"#/components/schemas/CustomiserBackboardPriceMultiplierTarget"},"name":{"type":"string","description":"The finish's display name.","example":"Gloss Black"},"description":{"type":"string","description":"A description shown alongside the option; null clears it.","example":"A deep gloss black"},"hexcode":{"type":"string","description":"The colour the backboard is finished in, as a hex value; null clears it.","example":"#111111"},"opacity":{"type":"number","description":"How opaque the finish renders, from 0 to 1.","example":0.8},"applied_to_visualisation":{"type":"boolean","description":"Whether the storefront paints the backboard with this finish.","example":true},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"type":"number","description":"Minimum sign width, in centimetres, before the finish is offered; null clears it.","example":null},"min_height_cm":{"type":"number","description":"Minimum sign height, in centimetres, before the finish is offered; null clears it.","example":null},"base_price":{"type":"integer","description":"A flat amount added when the finish is chosen, in integer minor units.","example":500},"price_multiplier":{"type":"number","description":"Multiplies the price_multiplier_target when the finish is chosen.","example":1.2}}}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a support finish\n\nRemoves the support finish and returns a deletion receipt: a snapshot of\nexactly what was removed, so the caller can verify and report the change.\nA finish anything still references is refused with a 409\n`resource_in_use` listing the referencing rows — references are never\nsilently cleared. Requires `customisers:write` and an `Idempotency-Key`;\nretrying the same request and key replays the original receipt after the\nrow is gone.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteSupportFinish","parameters":[{"in":"header","name":"Idempotency-Key","example":"8f0a2c4e-6b7d-4f1a-8b5c-7d9e1f3a5b6c","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The receipt type discriminator (closed set).","enum":["support_finish_deletion_receipt"]},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"deleted":{"type":"boolean"},"resource":{"$ref":"#/components/schemas/CustomiserSupportFinishItem"}},"required":["object","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Support finish still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id.","example":"27","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a support finish update\n\nDry-runs the merge patch without persisting anything, returning the same\nproblems the update would return or a 200 verdict when it would succeed.\nNothing is written, no caches are touched, and no idempotency key is\nconsumed. The verdict is advisory. Requires `customisers:write`.","operationId":"validateUpdateSupportFinish","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardValidateUpdateRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id.","example":"27","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate a support finish delete\n\nDry-runs the delete without removing anything: an in-use finish returns\nthe identical 409 `resource_in_use` problem the delete would return, and\na deletable one returns a 200 verdict. Nothing is written and no\nidempotency key is consumed. The verdict is advisory. Requires\n`customisers:write`.","operationId":"validateDeleteSupportFinish","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportFinishValidateDeleteResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Support finish still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserBackboardResponse"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}/images/{image_slot}":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id.","example":"27","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which of the support finish's images to write (closed set): the swatch a shopper picks from, the example photograph beside it, or the texture the texture finish kind is tiled from.","example":"preview_image","required":true,"schema":{"type":"string"}}],"put":{"summary":"Attach a support finish image\n\nTakes the raster image of a staged upload and puts it in one of the\nsupport finish's three image slots, returning the support finish with\nthat slot's `*_url` pointing at the new file. Requires\n`customisers:write` and an `Idempotency-Key`.\n\nCreate the upload first with `POST /api/v3/uploads` — purpose\n`option_image` for `preview_image` or `example_image`,\n`option_texture_image` for `texture_image` — send the bytes to the\npresigned target it returns, then send that upload's id here. An upload\nwhose purpose is not the slot's answers 422 `upload_purpose_mismatch`.\nThe upload is single use: attaching it a second time answers 409, and\neach slot needs its own upload.\n\nOnly PNG, JPEG and WebP are accepted. SVG is refused when the upload is\ncreated, because the option-image path stores files as they arrive and\ndoes not sanitise them.\n\nThe bytes are verified here, not at create: the object must exist, be\nwithin the purpose's cap, start with the magic bytes of the type it was\ndeclared as, and carry a readable image header. A file that fails leaves\nthe upload `rejected` with the failure's code, and attaching it again\nreplays that code — fix the file and create a new upload rather than\nretrying.\n\nAttaching to a slot that already holds an image replaces it. The\nprevious file is deleted once the new one is committed, so any URL\npublished for it stops resolving.\n\nAttaching to `texture_image` is what makes the `texture` finish kind\navailable: send the image here first, then set `finish_kind` on the\nfinish itself.\n\nThe attach clears the customiser's cached storefront configuration and\nruns the advisory language sync. It never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"attachSupportFinishImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"d3f7a1b2-9c4e-4d58-b0a6-1e2f3c4d5e6f","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"finish_kind":{"type":"string"},"hexcode":{"type":"string"},"opacity":{"type":"integer"},"applied_to_visualisation":{"type":"boolean"},"backboard_ids":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"min_width_cm":{"$ref":"#/components/schemas/NullableString"},"min_height_cm":{"$ref":"#/components/schemas/NullableString"},"min_size_rule":{"type":"string"},"base_price":{"type":"integer"},"currency":{"type":"string"},"price_multiplier":{"type":"integer"},"price_multiplier_target":{"type":"string"},"sort_order":{"type":"integer"},"preview_image_url":{"type":"string"},"example_image_url":{"$ref":"#/components/schemas/NullableString"},"texture_image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Upload already attached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]},"delete":{"summary":"Delete a support finish image\n\nClears one of the support finish's image slots and deletes the stored\nfile, returning a receipt naming what was removed and the support finish\nas it now stands. Requires `customisers:write` and an `Idempotency-Key`.\nDeleting from a slot that holds no image answers 404.\n\nDeleting the texture image of a finish whose `finish_kind` is `texture`\nis refused with 422 `texture_image_required`: the finish renders from\nthat image, and the storefront could not draw the backboard without it.\nChange `finish_kind` on the finish first, then delete the image.\n\nThe delete clears the customiser's cached storefront configuration and\nruns the advisory language sync, and never regenerates Shopify products,\nproduct images, or the product cache.","operationId":"deleteSupportFinishImage","parameters":[{"in":"header","name":"Idempotency-Key","example":"b8c1d2e3-4f5a-4b6c-9d7e-8f9a0b1c2d3e","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/BackboardImageObject"},"slot":{"type":"string","description":"The image slot that was cleared.","enum":["preview_image","example_image","texture_image"]},"customiser_id":{"type":"integer"},"support_finish_id":{"type":"integer"},"deleted":{"type":"boolean"},"removed_file":{"$ref":"#/components/schemas/BackboardImageRemovedFile"},"resource":{"$ref":"#/components/schemas/CustomiserSupportFinishItem"}},"required":["object","slot","resource"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Option write budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}/images/{image_slot}/validate-update":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id.","example":"27","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to write: preview_image, example_image or texture_image.","example":"preview_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a support finish image attach\n\nDry-runs the attach: the slot must exist on this support finish, the\nupload must exist for this store and this API key, and its purpose must\nbe the slot's. Nothing is written, the upload is not consumed, and no\nidempotency key is spent. Requires `customisers:write`.\n\nThe staged bytes are deliberately not read, so this cannot tell you\nwhether the file is a usable image — that verdict only exists once the\nattach reads the object, and reading it here would let a preflight mark\na good upload rejected. Treat a valid verdict as \"the upload is\nattachable\", not \"the file is sound\".","operationId":"validateAttachSupportFinishImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse12"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown support finish, slot or upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Purpose mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackboardImageRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/customisers/{customiser_id}/support-finishes/{support_finish_id}/images/{image_slot}/validate-delete":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id.","example":"1","required":true,"schema":{"type":"integer"}},{"in":"path","name":"support_finish_id","description":"The support finish id.","example":"27","required":true,"schema":{"type":"integer"}},{"in":"path","name":"image_slot","description":"Which image to remove: preview_image, example_image or texture_image.","example":"preview_image","required":true,"schema":{"type":"string"}}],"post":{"summary":"Validate a support finish image delete\n\nDry-runs the delete: an empty slot answers the identical 404 the delete\nwould, a texture image the `texture` finish kind still needs answers the\nidentical 422, and anything else returns a 200 verdict. Nothing is\nwritten, nothing is removed, and no idempotency key is consumed. The\nverdict is advisory. Requires `customisers:write`.","operationId":"validateDeleteSupportFinishImage","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageValidateDeleteResponse12"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Empty slot","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Texture image still in use","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Customiser support finishes"],"security":[{"default":["customisers:write"]}]}},"/api/orders":{"parameters":[],"post":{"summary":"Create a legacy order\n\nDeprecated. Use `POST /api/v2/orders` for new integrations.","operationId":"createALegacyOrderDeprecatedUsePOSTapiv2ordersForNewIntegrations","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserProductResponse"}}}}},"tags":["Legacy"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string","description":"Your platform's internal order ID.","example":"123456"},"order_number":{"type":"string","description":"The human-readable order number.","example":"SC-1001"},"order_total":{"type":"number","description":"The total order value.","example":86.34},"order_currency":{"type":"string","description":"The order currency.","example":"USD"},"email":{"$ref":"#/components/schemas/NullableString"},"products":{"$ref":"#/components/schemas/OrderProducts"},"shipping_address":{"type":["object","null"],"description":"The shipping address for the order.","example":[],"properties":{"name":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"},"address1":{"$ref":"#/components/schemas/NullableString"},"address2":{"$ref":"#/components/schemas/NullableString"},"city":{"$ref":"#/components/schemas/NullableString"},"province":{"$ref":"#/components/schemas/NullableString"},"country":{"$ref":"#/components/schemas/NullableString"},"zip":{"$ref":"#/components/schemas/NullableString"}}},"shipping_line":{"$ref":"#/components/schemas/NullableString"}},"required":["order_id","order_number","products"]}}}}}},"/api/customisers/{customiser}/products":{"parameters":[{"in":"path","name":"customiser","description":"The Sign Customiser ID tied to this product.","example":"42","required":true,"schema":{"type":"string"}}],"post":{"summary":"Sync a legacy product\n\nDeprecated. Use the current custom integration flow for new builds.","operationId":"syncALegacyProductDeprecatedUseTheCurrentCustomIntegrationFlowForNewBuilds","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserProductResponse"}}}}},"tags":["Legacy"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","description":"Your platform's product ID.","example":"product-987654321"},"cart":{"$ref":"#/components/schemas/CustomiserProductCart"},"price_breakdown":{"$ref":"#/components/schemas/CustomiserProductCart"},"custom_background_path":{"$ref":"#/components/schemas/NullableString"},"custom_background_original_path":{"$ref":"#/components/schemas/NullableString"},"product_type_paths":{"$ref":"#/components/schemas/CustomiserProductProductTypePaths"},"logo_original_path":{"$ref":"#/components/schemas/NullableString"}},"required":["product_id","cart","price_breakdown"]}}}}}},"/api/v3/orders":{"parameters":[],"get":{"summary":"List orders\n\nList orders accepted and stored by Sign Customiser for the authenticated\nStore, newest first. `received` means only that the order was accepted\ninto Sign Customiser; it does not claim payment, manufacturing,\nfulfilment, shipping, refund, or cancellation state. `line_item_count`\ncounts modern products only. Legacy cached products remain available to\nthe established CSV export but are deliberately absent from v3 order\nprojections and do not match `filter[customiser_id]`.\n\nFollow `links.next` while `pagination.has_more` is true. Cursors are\nopaque and are valid only with the exact filters that issued them.\nUnsupported parameters and filters are rejected rather than ignored.\nRequires `orders:read`; results are always scoped to the Store belonging\nto the API key. This read is safe to retry.","operationId":"listOrders","parameters":[{"in":"query","name":"filter[status]","description":"Order acceptance status. This set is expected to grow; tolerate unknown response values.","example":"received","required":false,"schema":{"$ref":"#/components/schemas/OrderParameter"}},{"in":"query","name":"limit","description":"Number of orders per page, from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of orders per page, from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"The opaque pagination.next_cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"The opaque pagination.next_cursor from the previous page.","example":null}},{"in":"query","name":"filter[created_from]","description":"Include orders created on or after this UTC date (YYYY-MM-DD).","example":"2026-07-01","required":false,"schema":{"type":"string","description":"Include orders created on or after this UTC date (YYYY-MM-DD)."}},{"in":"query","name":"filter[created_to]","description":"Include orders created on or before this UTC date (YYYY-MM-DD).","example":"2026-07-19","required":false,"schema":{"type":"string","description":"Include orders created on or before this UTC date (YYYY-MM-DD)."}},{"in":"query","name":"filter[customiser_id]","description":"Include orders with a modern line item from this customiser.","example":42,"required":false,"schema":{"type":"integer","description":"Include orders with a modern line item from this customiser."}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"The page of order summaries. Each status uses the expected-to-grow enum documented by the show operation.","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"store_order_id":{"type":"string"},"store_order_number":{"type":"string"},"status":{"$ref":"#/components/schemas/OrderParameter"},"status_display_name":{"type":"string"},"total_amount":{"type":"integer"},"currency":{"type":"string"},"line_item_count":{"type":"integer"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["status"]}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"422":{"description":"Invalid filter or cursor","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Orders"],"security":[{"default":["orders:read"]}]},"post":{"summary":"Create an order\n\nStore one order received from an enabled custom integration for the\nauthenticated Store. Every `product_id` must be a modern product created\nthrough the same integration and Store. The 201 response means Sign\nCustomiser committed the order; it does not claim payment, manufacturing,\nfulfilment, shipping, refund, or cancellation state. Requires\n`orders:write`.\n\n`total_amount` is the integration's external grand total in integer minor\nunits. Sign Customiser separately derives its app total from the stored\nunit prices and quantities, so callers cannot override the platform fee\nbasis. `currency` must equal the Store and every referenced product\ncurrency. Customer and address values are optional snapshots; request\npayloads are never retained as raw JSON.\n\nSign Customiser durably schedules the established manufacturer order\nemails, order analytics, order-created webhook, and onboarding\nsynchronisation with the order. Manufacturer recipients and analytics\nevents have independent retry units. A queue publishing failure rolls the\norder back. Delivery by external providers is asynchronous. Repeating the\nexact method, path, body, credential, and `Idempotency-Key` replays the\noriginal 201 response and does not schedule those effects again. Retry a 409\n`idempotency_key_in_flight` with the same key after `retry_after`; use the\noriginal body or a new key for an idempotency conflict. This v3 operation\nrejects a `store_order_id` already present for the integration and\nserialises concurrent v3 creations. During migration, do not send the same\nlogical order through legacy and v3 writes in parallel.","operationId":"createOrder","parameters":[{"in":"header","name":"Idempotency-Key","example":"11da2e11-24ca-41ae-99cc-f88c87d2a645","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown or foreign resource","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"External order already exists","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Integration does not accept writes","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"integration_id":{"type":"integer","description":"Enabled custom integration id belonging to the Store.","example":17},"store_order_id":{"type":"string","description":"Stable order id in the integration.","example":"ext-order-1001"},"store_order_number":{"type":"string","description":"Display order number in the integration.","example":"#1001"},"total_amount":{"type":"integer","description":"External grand total in minor currency units.","example":25800},"currency":{"type":"string","description":"Uppercase ISO 4217 Store currency.","example":"AUD"},"shipping_method":{"$ref":"#/components/schemas/NullableString"},"customer":{"type":["object","null"],"description":"Nullable customer snapshot.","example":{"first_name":"Ada","last_name":"Lovelace","email":"ada@example.com","phone":"+61 400 000 001"},"properties":{"first_name":{"$ref":"#/components/schemas/NullableString"},"last_name":{"$ref":"#/components/schemas/NullableString"},"email":{"$ref":"#/components/schemas/NullableString"},"phone":{"$ref":"#/components/schemas/NullableString"}},"additionalProperties":false},"billing_address":{"$ref":"#/components/schemas/OrderBillingAddress2"},"shipping_address":{"$ref":"#/components/schemas/OrderBillingAddress2"},"line_items":{"type":"array","description":"One to 100 unique modern products and quantities.","example":[{"product_id":731,"quantity":2}],"items":{"type":"object","properties":{"product_id":{"type":"integer","description":"Product id returned by the product write operation.","example":731},"quantity":{"type":"integer","description":"Quantity from 1 to 10,000.","example":2}},"required":["product_id","quantity"],"additionalProperties":false}}},"required":["integration_id","store_order_id","store_order_number","total_amount","currency","line_items"],"additionalProperties":false}}}},"security":[{"default":["orders:write"]}]}},"/api/v3/orders/export":{"parameters":[],"post":{"summary":"Queue an order CSV export\n\nQueue the established CSV export for an inclusive UTC creation-date\nrange. The email recipient is the authenticated Store's contact email\nand cannot be overridden. The range may contain at most 366 days and\n1,000 orders, and its estimated CSV size must stay within the export\nceiling; choose a shorter range when either bound is exceeded. Unlike the\nv3 read projection, the frozen CSV contract still includes cached legacy\norder rows. Requires `orders:write`. Supply an `Idempotency-Key`;\nreplaying the same request does not queue or email a second export.","operationId":"exportOrders","parameters":[{"in":"header","name":"Idempotency-Key","example":"11da2e11-24ca-41ae-99cc-f88c87d2a645","schema":{"type":"string"}}],"responses":{"202":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"status":{"type":"string"},"recipient_email":{"type":"string"},"created_from":{"type":"string"},"created_to":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid or oversized export","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Export rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"created_from":{"type":"string","description":"First included UTC date, in YYYY-MM-DD format.","example":"2026-07-01"},"created_to":{"type":"string","description":"Last included UTC date, in YYYY-MM-DD format.","example":"2026-07-19"}},"required":["created_from","created_to"]}}}},"security":[{"default":["orders:write"]}]}},"/api/v3/orders/{order_id}":{"parameters":[{"in":"path","name":"order_id","description":"The order id returned by the list operation.","example":"123","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Show an order\n\nReturn one Store-scoped order with customer and address snapshots plus\nits modern line items. Cached legacy products are never serialised; an\nolder cached-only order therefore returns an empty `line_items` array.\nMoney uses integer minor units with an explicit currency, dimensions are\ncentimetres, and timestamps are ISO 8601 UTC. Requires `orders:read` and\nis safe to retry.","operationId":"getOrder","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"404":{"description":"Unknown or foreign order","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Orders"],"security":[{"default":["orders:read"]}]}},"/api/v3/orders/{order_id}/resend":{"parameters":[{"in":"path","name":"order_id","description":"The order id returned by the list operation.","example":"123","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Queue an order email resend\n\nQueue the established order email again for the manufacturer recipient\nconfigured by the Store. The operation accepts no request fields: a\ncaller cannot choose or override any recipient. Requires `orders:write`.\nSupply a unique `Idempotency-Key` for each logical resend; retrying the\nsame body and key replays the 202 response without queuing another email.\nDistinct attempts are limited to two per API key per minute.","operationId":"resendOrderEmail","parameters":[{"in":"header","name":"Idempotency-Key","example":"8cde76ce-10e9-4fc2-9d31-93af24277e9f","schema":{"type":"string"}}],"responses":{"202":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"order_id":{"type":"integer"},"status":{"type":"string"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown or foreign order","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown request field","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse2"}}}},"429":{"description":"Resend rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Orders"],"security":[{"default":["orders:write"]}]}},"/api/v2/customisers/{customiser}/pricing":{"parameters":[{"in":"path","name":"customiser","description":"The customiser ID.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get customiser pricing\n\nRetrieve all pricing configuration for a customiser.\n\nThe `pricing_model` value is the internal enum name for the customiser's\npricing model. The current product names are: `FIXED_WIDTH` is the Simple\nLetter Model, `MATERIAL_LENGTH` is the Advanced Letter Model, and\n`FRAME_FIT` is the Frame Fit Model. `FIXED_HEIGHT` is the deprecated\nlegacy Fixed Height Model and is rejected with a 422 — the supported\nfixed-height sizing path is the Simple Letter Model (`FIXED_WIDTH`) with a\n`HEIGHT` height strategy on its sizes.\n\nError responses always include a human-readable `message` field.\nValidation failures also include an `errors` object keyed by field path.","operationId":"getCustomiserPricingRetrieveAllPricingConfigurationForACustomiserThepricingModelValueIsTheInternalEnumNameForTheCustomisersPricingModelTheCurrentProductNamesAreFIXEDWIDTHIsTheSimpleLetterModelMATERIALLENGTHIsTheAdvancedLetterModelAndFRAMEFITIsTheFrameFitModelFIXEDHEIGHTIsTheDeprecatedLegacyFixedHeightModelAndIsRejectedWithA422TheSupportedFixedHeightSizingPathIsTheSimpleLetterModelFIXEDWIDTHWithAHEIGHTHeightStrategyOnItsSizesErrorResponsesAlwaysIncludeAHumanReadablemessageFieldValidationFailuresAlsoIncludeAnerrorsObjectKeyedByFieldPath","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse"}}}},"401":{"description":"Missing or invalid API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"403":{"description":"Customiser belongs to another store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"404":{"description":"Unknown customiser ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"422":{"description":"Deprecated FIXED_HEIGHT customiser","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}}},"tags":["Pricing"],"deprecated":true},"put":{"summary":"Update customiser pricing\n\nReplace all pricing configuration for a customiser.\n\nThis is a full replacement: sizes and price-list rows omitted from the\npayload are deleted. A replacement that would delete size or price-list\nrecords still referenced by fonts or settings is rejected with a 409 and\nno changes are applied.\n\nThe `pricing_model` value is the internal enum name for the customiser's\npricing model. The current product names are: `FIXED_WIDTH` is the Simple\nLetter Model, `MATERIAL_LENGTH` is the Advanced Letter Model, and\n`FRAME_FIT` is the Frame Fit Model. This endpoint cannot switch a\ncustomiser to a different pricing model, and the deprecated `FIXED_HEIGHT`\nlegacy model is rejected with a 422.\n\nError responses always include a human-readable `message` field.\nValidation failures also include an `errors` object keyed by field path.","operationId":"updateCustomiserPricingReplaceAllPricingConfigurationForACustomiserThisIsAFullReplacementSizesAndPriceListRowsOmittedFromThePayloadAreDeletedAReplacementThatWouldDeleteSizeOrPriceListRecordsStillReferencedByFontsOrSettingsIsRejectedWithA409AndNoChangesAreAppliedThepricingModelValueIsTheInternalEnumNameForTheCustomisersPricingModelTheCurrentProductNamesAreFIXEDWIDTHIsTheSimpleLetterModelMATERIALLENGTHIsTheAdvancedLetterModelAndFRAMEFITIsTheFrameFitModelThisEndpointCannotSwitchACustomiserToADifferentPricingModelAndTheDeprecatedFIXEDHEIGHTLegacyModelIsRejectedWithA422ErrorResponsesAlwaysIncludeAHumanReadablemessageFieldValidationFailuresAlsoIncludeAnerrorsObjectKeyedByFieldPath","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse"}}}},"401":{"description":"Missing or invalid API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"403":{"description":"Customiser belongs to another store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"404":{"description":"Unknown customiser ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"409":{"description":"Replacement would delete referenced records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse2"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"errors":{"type":"object","properties":{"pricing.sizes":{"$ref":"#/components/schemas/OrderProducts"}}}}}}}}},"tags":["Pricing"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pricing":{"type":"object","description":"The replacement pricing configuration.","example":[],"properties":{"pricing_model":{"$ref":"#/components/schemas/NullableString"},"sizes":{"type":"array","description":"Size records.","example":[[]],"items":{"type":"object","properties":{"size_id":{"$ref":"#/components/schemas/NullableInteger"},"client_key":{"$ref":"#/components/schemas/NullableString"},"name":{"type":"string","example":"Small"},"description":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"$ref":"#/components/schemas/NullableInteger"},"char_limit":{"$ref":"#/components/schemas/NullableInteger"},"line_limit":{"$ref":"#/components/schemas/NullableInteger"},"min_char":{"$ref":"#/components/schemas/NullableInteger"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"line_height_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_strategy":{"type":["string","null"],"example":"WIDTH","enum":["WIDTH","HEIGHT","DYNAMIC","FIXED",null]},"height_measurement_mode":{"type":["string","null"],"example":"LINE_HEIGHT","enum":["LINE_HEIGHT","LETTER_HEIGHT",null]},"width_multiplier":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"}},"required":["name"]}},"base_pricing":{"type":"object","description":"Base pricing settings.","example":[],"properties":{"mode":{"type":"string","example":"included_in_price_lists"},"rows":{"type":"array","example":[[]],"items":{"type":"object","properties":{"size_id":{"$ref":"#/components/schemas/NullableInteger"},"size_key":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/CustomiserProductProductTypePaths"},"width_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableNumber"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableNumber"},"width_modifier":{"$ref":"#/components/schemas/NullableNumber"},"height_modifier":{"$ref":"#/components/schemas/NullableNumber"},"length_modifier":{"$ref":"#/components/schemas/NullableNumber"}}}}},"required":["mode","rows"]},"price_lists":{"type":"array","description":"Price lists.","example":[[]],"items":{"type":"object","properties":{"pricing_id":{"$ref":"#/components/schemas/NullableInteger"},"name":{"type":"string","example":"Standard"},"letter_pricing_mode":{"type":"string","description":"Simple and Advanced Letter mode: EVERY_LETTER_COST or PRICE_BY_POSITION. Omitted creates default to EVERY_LETTER_COST; omitted updates preserve the stored mode.","example":"EVERY_LETTER_COST"},"letter_pricing_method":{"type":["string","null"],"example":"material_length","enum":["material_length","fixed_letter",null]},"shipping":{"type":["object","null"],"example":null,"properties":{"calculation":{"type":["string","null"],"example":"physical","enum":["physical","volumetric",null]},"volumetric_divisor":{"$ref":"#/components/schemas/NullableNumber"},"add_weight_to_product":{"$ref":"#/components/schemas/NullableBoolean"}}},"rows":{"type":"array","example":[[]],"items":{"type":"object","properties":{"size_id":{"$ref":"#/components/schemas/NullableInteger"},"size_key":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/CustomiserProductProductTypePaths"},"letter_price":{"$ref":"#/components/schemas/CustomiserProductProductTypePaths"},"letter_price_by_position":{"$ref":"#/components/schemas/CustomiserProductCart"},"price_per_cm":{"$ref":"#/components/schemas/NullableNumber"},"price_per_letter":{"$ref":"#/components/schemas/NullableNumber"},"price_per_letter_by_position":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition"},"max_price_multiplier":{"$ref":"#/components/schemas/NullableNumber"},"width_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_limit_cm":{"$ref":"#/components/schemas/NullableNumber"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableNumber"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableNumber"},"width_modifier":{"$ref":"#/components/schemas/NullableNumber"},"height_modifier":{"$ref":"#/components/schemas/NullableNumber"},"length_modifier":{"$ref":"#/components/schemas/NullableNumber"}}}}},"required":["name","rows"]}},"sheet_pricing":{"type":["object","null"],"example":null,"properties":{"pricings":{"type":"array","example":[[]],"items":{"type":"object","properties":{"cmLimitWidth":{"type":"number","example":4326.41688},"cmLimitHeight":{"type":"number","example":4326.41688},"basePrice":{"type":"integer","description":"Must be at least 0.","example":77},"pricePerSqCm":{"type":"number","description":"Must be at least 0.","example":8}},"required":["cmLimitWidth","cmLimitHeight","basePrice","pricePerSqCm"]}}},"required":["pricings"]}},"required":["sizes","base_pricing","price_lists"]}},"required":["pricing"]}}}}}},"/api/v3/customisers/{customiser_id}/pricing":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get customiser pricing\n\nRetrieve the customiser's full pricing configuration as one canonical\ndocument. The document's shape follows its `pricing_model`:\n`simple_letter` prices per letter at fixed sign widths (sizes carry a\n`sizing_strategy` of `fixed_width` or `fixed_height` and a\n`height_measurement_mode` of `line_height` or `letter_height`, price\nlist rows carry per-line `base_price`/`letter_price` objects plus\noptional `letter_price_by_position` schedules, and an optional\n`sheet_pricing` section adds backing-sheet bands);\n`advanced_letter` prices from material length (price lists carry a\n`letter_pricing_method` of `material_length` or `fixed_letter`, a\n`shipping` configuration, and rows with rates and dimension limits;\nfixed-letter rows may carry `price_per_letter_by_position` schedules);\n`frame_fit` prices by frame area (exactly one price list with\narea-rate rows). Amount fields (`base_price`, `letter_price` lines,\n`price_per_letter`, and both position-price schedules) are integer minor\nunits of `currency`; the rate\nfields `price_per_cm` and `price_per_sq_cm` are decimal major currency\nunits per centimetre (or square centimetre); dimensions are\ncentimetres in `_cm` fields. `currency` is the store's configured ISO\n4217 currency, or null on Shopify-backed stores that leave the\nstorefront currency in charge. The operation is read-only, has no side\neffects, and is always safe to retry. Requires the `pricing:read`\nscope; use this document as the base for `PATCH`/`PUT` writes and the\nvalidate dry-run.\n\nA 404 problem with code `resource_not_found` means the customiser does\nnot exist on the store your API key belongs to. A 422 problem with\ncode `legacy_pricing_model` means the customiser still uses the\nretired fixed-height pricing model, which has no pricing document on\nthis API (its replacement is `simple_letter` with `fixed_height`\nsizing). A 401 problem means the key is missing, invalid, or expired;\na 403 problem with code `insufficient_scope` means the key lacks\n`pricing:read`; a 429 problem means too many requests — wait\n`retry_after` seconds, then retry. Error responses use RFC 9457\nproblem+json with a machine-readable `code` and a `request_id` to\nquote when contacting support.","operationId":"getCustomiserPricing","responses":{"200":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"simple_letter customiser","type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/CustomiserPricingOneOfObject"},"customiser_id":{"type":"integer","description":"The customiser this pricing document belongs to."},"pricing_model":{"$ref":"#/components/schemas/CustomiserPricingOneOfPricingModel"},"pricing_model_display_name":{"type":"string","description":"The pricing model's human display name (e.g. \"Simple Letter\"). Display only — parse pricing_model, never this label."},"currency":{"$ref":"#/components/schemas/NullableString"},"sizes":{"$ref":"#/components/schemas/CustomiserPricingOneOfSizes"},"base_pricing":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/CustomiserPricingOneOfBasePricingMode"},"rows":{"type":"array","items":{"type":"object","properties":{"size_id":{"type":"integer"},"size_key":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice"}}}}},"required":["mode"],"description":"The base price configuration. mode is a closed set: included_in_price_lists (base prices live inside price list rows; rows is empty), single_base_price_by_size (simple_letter only: one base price per size, rows carry size_id/size_key and per-line base_price), or single_base_price_by_size_boundary (advanced_letter and frame_fit: rows carry base_price in minor units with width_limit_cm/height_limit_cm boundaries)."},"price_lists":{"type":"array","description":"The customiser's price lists — merchants attach each font (and icon set) to one of these lists, which is how different letter styles price differently; the attachment itself is managed outside this document. Simple and Advanced Letter lists always carry letter_pricing_mode (closed set: every_letter_cost, price_by_position). simple_letter rows reference a size and carry per-line base_price/letter_price plus optional letter_price_by_position schedules keyed line_1 to line_10; all schedule entries are integer minor currency units and the final entry repeats beyond the configured list. advanced_letter lists add letter_pricing_method (material_length or fixed_letter); fixed_letter rows may carry price_per_letter_by_position schedules in integer minor units. frame_fit has exactly one list and never carries letter-pricing mode or schedules. price_per_cm and price_per_sq_cm are decimal major currency units — they are rates, not amounts.","items":{"type":"object","properties":{"price_list_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"$ref":"#/components/schemas/CustomiserFontTierLetterPricingMode"},"rows":{"type":"array","items":{"type":"object","properties":{"size_id":{"type":"integer"},"size_key":{"$ref":"#/components/schemas/NullableString"},"base_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice"},"letter_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice"},"letter_price_by_position":{"$ref":"#/components/schemas/NullableObject"}}}}}}},"sheet_pricing":{"type":["object","null"],"properties":{"rows":{"type":"array","items":{"type":"object","properties":{"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"base_price":{"type":"integer"},"price_per_sq_cm":{"type":"number"}}}}},"description":"simple_letter only: the optional backing-sheet pricing section, or null when not configured (the key is absent entirely on other pricing models). rows carry width_limit_cm and height_limit_cm band boundaries, base_price in integer minor units, and price_per_sq_cm as a decimal major-unit rate."}},"required":["object","customiser_id","pricing_model","pricing_model_display_name","currency","sizes","base_pricing","price_lists","sheet_pricing"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},{"description":"advanced_letter customiser","type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/CustomiserPricingOneOfObject"},"customiser_id":{"type":"integer","description":"The customiser this pricing document belongs to."},"pricing_model":{"$ref":"#/components/schemas/CustomiserPricingOneOfPricingModel"},"pricing_model_display_name":{"type":"string","description":"The pricing model's human display name (e.g. \"Simple Letter\"). Display only — parse pricing_model, never this label."},"currency":{"$ref":"#/components/schemas/NullableString"},"sizes":{"type":"array","description":"The customiser's sizes in display order. Every size reports the same key set; fields that do not apply to the pricing model are null. Common fields: size_id (integer), client_key (write-side correlation key, always null on reads), name, description, sort_order, min_char. simple_letter sizes use char_limit, line_limit, width_cm, line_height_cm, sizing_strategy (closed set: fixed_width, fixed_height — how the sign scales; internal legacy spellings never appear) and height_measurement_mode (closed set: line_height, letter_height). advanced_letter sizes use width_multiplier. frame_fit sizes use width_cm, height_cm, char_limit, min_char and length_cm, where length_cm is physical depth. Dimensions are centimetres.","items":{"type":"object","properties":{"size_id":{"type":"integer"},"client_key":{"$ref":"#/components/schemas/NullableString"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"line_limit":{"$ref":"#/components/schemas/NullableString"},"min_char":{"type":"integer"},"width_cm":{"$ref":"#/components/schemas/NullableString"},"height_cm":{"$ref":"#/components/schemas/NullableString"},"line_height_cm":{"$ref":"#/components/schemas/NullableString"},"width_multiplier":{"type":"number"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"$ref":"#/components/schemas/NullableString"},"height_measurement_mode":{"$ref":"#/components/schemas/NullableString"}}}},"base_pricing":{"$ref":"#/components/schemas/CustomiserPricingOneOfBasePricing"},"price_lists":{"type":"array","description":"The customiser's price lists — merchants attach each font (and icon set) to one of these lists, which is how different letter styles price differently; the attachment itself is managed outside this document. Simple and Advanced Letter lists always carry letter_pricing_mode (closed set: every_letter_cost, price_by_position). simple_letter rows reference a size and carry per-line base_price/letter_price plus optional letter_price_by_position schedules keyed line_1 to line_10; all schedule entries are integer minor currency units and the final entry repeats beyond the configured list. advanced_letter lists add letter_pricing_method (material_length or fixed_letter); fixed_letter rows may carry price_per_letter_by_position schedules in integer minor units. frame_fit has exactly one list and never carries letter-pricing mode or schedules. price_per_cm and price_per_sq_cm are decimal major currency units — they are rates, not amounts.","items":{"type":"object","properties":{"price_list_id":{"type":"integer"},"name":{"type":"string"},"letter_pricing_mode":{"$ref":"#/components/schemas/CustomiserFontTierLetterPricingMode"},"letter_pricing_method":{"type":"string"},"shipping":{"$ref":"#/components/schemas/CustomiserLengthPricingItemShipping"},"rows":{"type":"array","items":{"type":"object","properties":{"base_price":{"type":"integer"},"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"length_limit_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_sq_cm":{"$ref":"#/components/schemas/NullableString"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableString"},"width_modifier":{"$ref":"#/components/schemas/NullableString"},"height_modifier":{"$ref":"#/components/schemas/NullableString"},"length_modifier":{"$ref":"#/components/schemas/NullableString"},"max_price_multiplier":{"type":"integer"},"price_per_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_letter":{"type":"integer"},"price_per_letter_by_position":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition2"}}}}}}}},"required":["object","customiser_id","pricing_model","pricing_model_display_name","currency","sizes","base_pricing","price_lists"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}},{"description":"frame_fit customiser","type":"object","properties":{"data":{"type":"object","properties":{"object":{"$ref":"#/components/schemas/CustomiserPricingOneOfObject"},"customiser_id":{"type":"integer","description":"The customiser this pricing document belongs to."},"pricing_model":{"$ref":"#/components/schemas/CustomiserPricingOneOfPricingModel"},"pricing_model_display_name":{"type":"string","description":"The pricing model's human display name (e.g. \"Simple Letter\"). Display only — parse pricing_model, never this label."},"currency":{"$ref":"#/components/schemas/NullableString"},"sizes":{"type":"array","description":"The customiser's sizes in display order. Every size reports the same key set; fields that do not apply to the pricing model are null. Common fields: size_id (integer), client_key (write-side correlation key, always null on reads), name, description, sort_order, min_char. simple_letter sizes use char_limit, line_limit, width_cm, line_height_cm, sizing_strategy (closed set: fixed_width, fixed_height — how the sign scales; internal legacy spellings never appear) and height_measurement_mode (closed set: line_height, letter_height). advanced_letter sizes use width_multiplier. frame_fit sizes use width_cm, height_cm, char_limit, min_char and length_cm, where length_cm is physical depth. Dimensions are centimetres.","items":{"type":"object","properties":{"size_id":{"type":"integer"},"client_key":{"$ref":"#/components/schemas/NullableString"},"name":{"type":"string"},"description":{"$ref":"#/components/schemas/NullableString"},"sort_order":{"type":"integer"},"char_limit":{"$ref":"#/components/schemas/NullableString"},"line_limit":{"$ref":"#/components/schemas/NullableString"},"min_char":{"type":"integer"},"width_cm":{"type":"number"},"height_cm":{"type":"integer"},"line_height_cm":{"$ref":"#/components/schemas/NullableString"},"width_multiplier":{"$ref":"#/components/schemas/NullableString"},"length_cm":{"$ref":"#/components/schemas/NullableString"},"sizing_strategy":{"$ref":"#/components/schemas/NullableString"},"height_measurement_mode":{"$ref":"#/components/schemas/NullableString"}}}},"base_pricing":{"$ref":"#/components/schemas/CustomiserPricingOneOfBasePricing"},"price_lists":{"type":"array","description":"The customiser's price lists — merchants attach each font (and icon set) to one of these lists, which is how different letter styles price differently; the attachment itself is managed outside this document. Simple and Advanced Letter lists always carry letter_pricing_mode (closed set: every_letter_cost, price_by_position). simple_letter rows reference a size and carry per-line base_price/letter_price plus optional letter_price_by_position schedules keyed line_1 to line_10; all schedule entries are integer minor currency units and the final entry repeats beyond the configured list. advanced_letter lists add letter_pricing_method (material_length or fixed_letter); fixed_letter rows may carry price_per_letter_by_position schedules in integer minor units. frame_fit has exactly one list and never carries letter-pricing mode or schedules. price_per_cm and price_per_sq_cm are decimal major currency units — they are rates, not amounts.","items":{"type":"object","properties":{"price_list_id":{"type":"integer"},"name":{"type":"string"},"shipping":{"$ref":"#/components/schemas/CustomiserLengthPricingItemShipping"},"rows":{"type":"array","items":{"type":"object","properties":{"base_price":{"type":"integer"},"width_limit_cm":{"type":"integer"},"height_limit_cm":{"type":"integer"},"length_limit_cm":{"$ref":"#/components/schemas/NullableString"},"price_per_sq_cm":{"type":"number"},"fixed_length_cm":{"$ref":"#/components/schemas/NullableString"},"width_modifier":{"$ref":"#/components/schemas/NullableString"},"height_modifier":{"$ref":"#/components/schemas/NullableString"},"length_modifier":{"$ref":"#/components/schemas/NullableString"}}}}}}}},"required":["object","customiser_id","pricing_model","pricing_model_display_name","currency","sizes","base_pricing","price_lists"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}]}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Retired pricing model","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Pricing"],"security":[{"default":["pricing:read"]}]},"put":{"summary":"Replace customiser pricing\n\nReplace the customiser's entire pricing configuration with the request\nbody — the full canonical document, exactly as the read operation\nreturns it (no envelope). This is an explicit full replacement: sizes\nand price lists omitted from the document are deleted (each carries its\nid — size_id, price_list_id — to update in place; omit the id to create\na new record, and use a size client_key with row size_key references to\nlink rows to sizes created in the same request). For partial updates\nprefer PATCH; to check a document without persisting anything use the\nvalidate operation. The read-only descriptor fields (object,\ncustomiser_id, pricing_model, pricing_model_display_name, currency) may\nbe echoed back unchanged but can never be changed here — in particular\nthe pricing model cannot be switched. Field semantics, units, and the\nper-model document shapes are documented on the read operation.\nRequires the `pricing:write` scope.\n\nA successful replacement immediately reprices the storefront: the\ncustomiser's cached configuration is invalidated and translated\nlanguage content is resynchronised. Send an `Idempotency-Key` header\n(1-255 visible ASCII characters, e.g. a UUID) to make the write safe\nto retry: repeating the same key with the same body replays the\noriginal response (marked `Idempotency-Replay: true`) without\nexecuting again; the same key with a different body is a 409 problem\nwith code `idempotency_key_conflict`; keys are retained for at least\n24 hours and are scoped to your credential, the HTTP method, and the\npath.\n\nValidation failures are 422 problems: `validation_failed` lists every\nfield error in `errors[]` with a JSON `pointer` and, for enum\nviolations, the `allowed_values`; `unknown_field` means the document\ncontains fields outside the schema (fix the pointers listed — nothing\nis ever silently ignored); `legacy_pricing_model` means the customiser\nstill uses the retired fixed-height model. A 409 problem with code\n`resource_in_use` means the document omits sizes, price lists, or\nsheet pricing that fonts or settings still reference — keep them in\nthe document or detach them first. A 400 problem means the body is not valid JSON or\nthe `Idempotency-Key` header is malformed. 401/403/404/429 behave as\non the read operation.","operationId":"replaceCustomiserPricing","responses":{"200":{"description":"Replaced document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse3"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserFormResponse3"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserLabelResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Pricing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","description":"Optional echo of the document type. Must be \"customiser_pricing\" when present.","example":"customiser_pricing"},"customiser_id":{"type":"integer","description":"Optional echo of the customiser id. Must match the path when present.","example":42},"pricing_model":{"type":"string","description":"Optional echo of the pricing model (read-only guard — the model cannot be switched; one of simple_letter, advanced_letter, frame_fit).","example":"simple_letter"},"pricing_model_display_name":{"type":"string","description":"Optional echo of the display name. Must match when present.","example":"Simple Letter"},"currency":{"type":"string","description":"Optional echo of the store currency (read-only guard). Must match when present.","example":"USD"},"sizes":{"type":"array","description":"The complete size collection. Sizes omitted from it are deleted (refused with a 409 while fonts still reference them).","example":[[]],"items":{"type":"object","properties":{"size_id":{"type":"integer","description":"The id of an existing size to update. Omit (or null) to create a new size.","example":7},"client_key":{"type":"string","description":"A correlation key for a newly created size, referenced by row size_key values in the same request. Must be unique across sizes.","example":"small"},"name":{"type":"string","description":"The size's display name.","example":"Small"},"description":{"type":"string","description":"The size's description.","example":"Up to 50cm wide"},"sort_order":{"type":"integer","description":"Display position (defaults to the array order).","example":1},"char_limit":{"type":"integer","description":"simple_letter and frame_fit: maximum characters.","example":20},"line_limit":{"type":"integer","description":"simple_letter only: maximum lines (1-10).","example":3},"min_char":{"type":"integer","description":"Minimum characters.","example":1},"width_cm":{"type":"number","description":"simple_letter and frame_fit: the size's width in centimetres.","example":50},"height_cm":{"type":"number","description":"frame_fit: the size's height in centimetres.","example":null},"line_height_cm":{"type":"number","description":"simple_letter: the per-line height in centimetres.","example":12},"sizing_strategy":{"type":"string","description":"simple_letter only: how the sign scales. One of fixed_width (the width is fixed, height follows the text) or fixed_height (the height is fixed, width follows the text).","example":"fixed_width"},"height_measurement_mode":{"type":"string","description":"simple_letter only: what the per-line value measures. One of line_height or letter_height.","example":"line_height"},"width_multiplier":{"type":"number","description":"advanced_letter only (required there): multiplies the rendered width into material length. Ignored-as-default elsewhere.","example":null},"length_cm":{"type":"number","description":"frame_fit: optional depth in centimetres.","example":null}},"required":["name"],"additionalProperties":false}},"base_pricing":{"type":"object","description":"The base pricing configuration.","example":[],"properties":{"mode":{"type":"string","description":"One of included_in_price_lists, single_base_price_by_size (simple_letter only), single_base_price_by_size_boundary (advanced_letter and frame_fit only).","example":"included_in_price_lists"},"rows":{"type":"array","description":"The base price rows (empty for included_in_price_lists).","example":[],"items":{"type":"object","properties":{"size_id":{"type":"integer","description":"simple_letter: the size this row prices (or use size_key).","example":7},"size_key":{"type":"string","description":"simple_letter: the client_key of a size created in this request.","example":null},"base_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice2"},"width_limit_cm":{"type":"number","description":"Boundary mode: the width boundary in centimetres.","example":null},"height_limit_cm":{"type":"number","description":"Boundary mode: the height boundary in centimetres.","example":null},"length_limit_cm":{"type":"number","description":"Boundary mode: the depth boundary in centimetres.","example":null}},"additionalProperties":false}}},"required":["mode","rows"],"additionalProperties":false},"price_lists":{"type":"array","description":"The complete price list collection. Lists omitted from it are deleted (refused with a 409 while fonts or settings still reference them). frame_fit requires exactly one list.","example":[[]],"items":{"type":"object","properties":{"price_list_id":{"type":"integer","description":"The id of an existing price list to update. Omit (or null) to create a new list.","example":10},"name":{"type":"string","description":"The price list's name.","example":"Standard"},"letter_pricing_mode":{"$ref":"#/components/schemas/CustomiserFontTierLetterPricingMode"},"letter_pricing_method":{"type":"string","description":"advanced_letter only (required there): material_length or fixed_letter.","example":null},"shipping":{"type":"object","description":"advanced_letter and frame_fit (required there): the shipping configuration.","example":[],"properties":{"calculation":{"type":"string","description":"physical or volumetric.","example":null},"volumetric_divisor":{"type":"integer","description":"Required for volumetric: the positive integer that divides width × height × depth.","example":null},"add_weight_to_product":{"type":"boolean","description":"Volumetric: emit the volumetric figure as product weight instead of a shipping charge.","example":null}},"additionalProperties":false},"rows":{"type":"array","description":"The list's pricing rows.","example":[[]],"items":{"type":"object","properties":{"size_id":{"type":"integer","description":"simple_letter: the size this row prices (or use size_key).","example":7},"size_key":{"type":"string","description":"simple_letter: the client_key of a size created in this request.","example":null},"base_price":{"$ref":"#/components/schemas/CustomiserPricingBasePricingRowBasePrice2"},"letter_price":{"$ref":"#/components/schemas/Object"},"letter_price_by_position":{"$ref":"#/components/schemas/NullableObject"},"price_per_cm":{"type":"number","description":"advanced_letter material_length: decimal major currency units per centimetre of material.","example":null},"price_per_letter":{"type":"integer","description":"advanced_letter fixed_letter: integer minor units per letter.","example":null},"price_per_letter_by_position":{"$ref":"#/components/schemas/CustomiserPricingPriceListRowPricePerLetterByPosition2"},"max_price_multiplier":{"type":"number","description":"advanced_letter: optional cap multiplier.","example":null},"width_limit_cm":{"type":"number","description":"The row's width band boundary in centimetres.","example":null},"height_limit_cm":{"type":"number","description":"The row's height band boundary in centimetres.","example":null},"length_limit_cm":{"type":"number","description":"The row's depth band boundary in centimetres.","example":null},"price_per_sq_cm":{"type":"number","description":"frame_fit: decimal major currency units per square centimetre.","example":null},"fixed_length_cm":{"type":"number","description":"Advanced rows: fixed sign depth in centimetres for non-Lightbox volumetric shipping.","example":null},"width_modifier":{"type":"number","description":"Shipping dimension modifier in centimetres.","example":null},"height_modifier":{"type":"number","description":"Shipping dimension modifier in centimetres.","example":null},"length_modifier":{"type":"number","description":"Lightbox-only shipping depth modifier in centimetres.","example":null}},"additionalProperties":false}}},"required":["name","rows"],"additionalProperties":false}},"sheet_pricing":{"type":"object","description":"simple_letter only: the backing-sheet pricing section, or null to remove it.","example":[],"properties":{"rows":{"type":"array","description":"The sheet bands.","example":[[]],"items":{"type":"object","properties":{"width_limit_cm":{"type":"number","description":"The band's width boundary in centimetres.","example":null},"height_limit_cm":{"type":"number","description":"The band's height boundary in centimetres.","example":null},"base_price":{"type":"integer","description":"The band's base amount in integer minor units.","example":null},"price_per_sq_cm":{"type":"number","description":"Decimal major currency units per square centimetre.","example":null}},"required":["width_limit_cm","height_limit_cm","base_price","price_per_sq_cm"],"additionalProperties":false}}},"additionalProperties":false}},"required":["sizes","base_pricing","price_lists"],"additionalProperties":false}}}},"security":[{"default":["pricing:write"]}]},"patch":{"summary":"Update customiser pricing\n\nPartially update the customiser's pricing with a JSON Merge Patch\n(RFC 7396) of the canonical document: the request body is a JSON\nobject containing only the properties to change. Properties omitted\nfrom the patch are unchanged; a null value removes a property (for\nexample `{\"sheet_pricing\": null}` removes the backing-sheet section);\narrays are replaced wholesale — to change one size or price list row,\nsend the complete new array (each entry carrying its size_id or\nprice_list_id to update in place). A merge patch can never edit one\narray element by itself. The patched result is validated and persisted\nas a full replacement, so the field rules, read-only descriptor\nguards, deletion semantics, and error catalogue are exactly those of\nthe replace operation; field semantics and per-model document shapes\nare documented on the read operation. An empty object `{}` is a valid\nno-op. Requires the `pricing:write` scope.\n\nA successful update immediately reprices the storefront: the\ncustomiser's cached configuration is invalidated and translated\nlanguage content is resynchronised. Send an `Idempotency-Key` header\n(1-255 visible ASCII characters, e.g. a UUID) to make the write safe\nto retry: repeating the same key with the same body replays the\noriginal response (marked `Idempotency-Replay: true`) without\nexecuting again; the same key with a different body is a 409 problem\nwith code `idempotency_key_conflict`; keys are retained for at least\n24 hours and are scoped to your credential, the HTTP method, and the\npath.\n\nA 422 problem with code `invalid_document` means the patch body is not\na JSON object. All other failures match the replace operation:\n`validation_failed` (pointers into the patched document plus\n`allowed_values` on enum violations), `unknown_field`,\n`legacy_pricing_model`, 409 `resource_in_use`, 409 idempotency\nconflicts, 400 `malformed_json`, and 401/403/404/429.","operationId":"updateCustomiserPricing","responses":{"200":{"description":"Patched document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomiserPricingResponse3"}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Idempotency key conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/PresetValidateDeleteResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Pricing"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"base_pricing":{"$ref":"#/components/schemas/CustomiserProductCart"}}}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/customisers/{customiser_id}/pricing/validate":{"parameters":[{"in":"path","name":"customiser_id","description":"The customiser id, from the list customisers operation.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Validate customiser pricing\n\nDry-run a pricing replacement without persisting anything. The request\nbody is the full canonical document, exactly as the replace operation\naccepts it, and the response is exactly what that replace would\nproduce: on an invalid document, the identical 422 problem\n(`validation_failed` with pointers and `allowed_values`,\n`unknown_field`, or `legacy_pricing_model`); on a document whose\nomissions would delete referenced records, the identical 409\n`resource_in_use` problem; and on a valid document, a 200\n`pricing_validation` confirmation — the same validation code path\nruns, only persistence is skipped. Nothing is written, no caches are\ntouched, and no idempotency key is consumed, so the operation is\nalways safe to repeat. To dry-run a PATCH, fetch the document, apply\nyour merge patch locally (RFC 7396), and validate the result.\nRequires the `pricing:write` scope (it is the write's dry-run twin).\n\nA 400 problem means the body is not valid JSON; a 422 problem with\ncode `invalid_document` means the body is not a JSON object.\n401/403/404/429 behave as on the read operation.","operationId":"validateCustomiserPricing","responses":{"200":{"description":"Document is valid","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"pricing_validation\" for this endpoint (closed set).","enum":["pricing_validation"]},"customiser_id":{"type":"integer","description":"The customiser the document was validated against."},"valid":{"type":"boolean","description":"Always true — an invalid document returns the write's error response instead of a 200."}},"required":["object","customiser_id","valid"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Malformed JSON body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown customiser","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Records still referenced","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"422":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BackboardImageResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Pricing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sizes":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"},"base_pricing":{"$ref":"#/components/schemas/CustomiserProductCart"},"price_lists":{"$ref":"#/components/schemas/CustomiserExtraNoColourExclusions"}},"required":["sizes","base_pricing","price_lists"]}}}},"security":[{"default":["pricing:write"]}]}},"/api/v3/products":{"parameters":[],"get":{"summary":"List products\n\nList the authenticated Store's modern products in descending creation\norder. Legacy ProductCache rows are excluded because they have a separate,\npotentially colliding identity. Each item contains integer minor-unit\nprices, the Store currency when valid, and ISO 8601 UTC timestamps. This\nread-only operation requires `products:read` and is safe to retry. A\nproduct `title` may contain customer-entered sign content, so protect API\nkeys with this scope accordingly.\n\nFollow `links.next` while `pagination.has_more` is true. Cursors are\nopaque and must be returned unchanged. No filters are supported. A 401\nmeans the API key is missing or invalid, 403 means it lacks the scope,\n422 means a parameter or cursor is invalid, and 429 should be retried\nafter the response's `retry_after` seconds.","operationId":"listProducts","parameters":[{"in":"query","name":"limit","description":"Products per page, from 1 to 100. Defaults to 20.","example":2,"required":false,"schema":{"type":"integer","description":"Products per page, from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"title":{"$ref":"#/components/schemas/NullableString"},"price_amount":{"type":"integer"},"compare_at_amount":{"$ref":"#/components/schemas/NullableInteger"},"currency":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"$ref":"#/components/schemas/NullableString"}},"required":["object","id","customiser_id","title","price_amount","compare_at_amount","currency","image_url","created_at","updated_at"]}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"422":{"description":"Invalid parameter or cursor","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Products"],"security":[{"default":["products:read"]}]}},"/api/v3/products/{product_id}":{"parameters":[{"in":"path","name":"product_id","description":"Product id returned by the list operation.","example":"731","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Show a product\n\nReturn one modern product owned by the authenticated Store, including\ndimensions and safe public artwork URLs. `title` and `description` are\nStore product metadata and may contain customer-entered sign content, so\nprotect keys with `products:read`. ProductCache history, raw node data,\nprovider payloads, underlying upload paths, upload/contact names, tokens,\nand emails are never exposed. The operation is read-only.\nUnknown and foreign ids return the same 404. Remove unknown query\nparameters after a 422. Retry 429 responses after `retry_after` seconds.","operationId":"getProduct","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"title":{"$ref":"#/components/schemas/NullableString"},"price_amount":{"type":"integer"},"compare_at_amount":{"$ref":"#/components/schemas/NullableInteger"},"currency":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"$ref":"#/components/schemas/NullableString"},"description":{"$ref":"#/components/schemas/NullableString"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"},"artwork":{"type":["object","null"],"properties":{"svg_url":{"$ref":"#/components/schemas/NullableString"},"eps_url":{"$ref":"#/components/schemas/NullableString"},"illustrator_pdf_url":{"$ref":"#/components/schemas/NullableString"}},"required":["svg_url","eps_url","illustrator_pdf_url"]}},"required":["object","id","customiser_id","title","price_amount","compare_at_amount","currency","image_url","created_at","updated_at","description","width_cm","height_cm","length_cm","artwork"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"404":{"description":"Unknown or foreign product","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Products"],"security":[{"default":["products:read"]}]}},"/api/v3/products/{product_id}/selections":{"parameters":[{"in":"path","name":"product_id","description":"Product id returned by the list operation.","example":"731","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Show product selections\n\nReturn the Store-local option ids captured for a modern product. Stable\ninteger `value` fields are separate from merchant-editable\n`display_name` labels. Canonical axes include `letter_type` and\n`support_finish`; the latter may report `finish_kind` and `colour_hex`.\nLookups are restricted to the product customiser, so stale or foreign\noption ids become null or are omitted. Requires `products:read` and has no\nside effects. Unknown and foreign product ids share one 404 response.","operationId":"getProductSelections","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"product_id":{"type":"integer"},"letter_type":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"size":{"type":["object","null"],"properties":{"value":{"type":"integer"},"display_name":{"$ref":"#/components/schemas/NullableString"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"}},"required":["value","display_name","width_cm","height_cm","length_cm"]},"support":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"support_finish":{"type":["object","null"],"properties":{"value":{"type":"integer"},"display_name":{"$ref":"#/components/schemas/NullableString"},"finish_kind":{"type":["string","null"],"description":"The finish kind. This set is expected to grow; tolerate unknown values.","enum":["colour","texture",null]},"colour_hex":{"$ref":"#/components/schemas/NullableString"}},"required":["value","display_name","finish_kind","colour_hex"]},"jacket":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"mounting":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"mounting_colour":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"backlight":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"material":{"$ref":"#/components/schemas/ProductSelectionBacklight"},"colours":{"$ref":"#/components/schemas/ProductSelectionColours"},"fonts":{"$ref":"#/components/schemas/ProductSelectionColours"}},"required":["object","product_id","letter_type","size","support","support_finish","jacket","mounting","mounting_colour","backlight","material","colours","fonts"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"404":{"description":"Unknown or foreign product","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Products"],"security":[{"default":["products:read"]}]}},"/api/v3/customisers/{customiser_id}/products":{"parameters":[{"in":"path","name":"customiser_id","description":"Store customiser receiving the product.","example":"1","required":true,"schema":{"type":"integer"}}],"post":{"summary":"Synchronise a product\n\nStore one product created by an enabled custom integration for the\nauthenticated Store and customiser. The response returns the modern\nproduct id used by order line items. Sign Customiser also creates a\nprivate compatibility projection for the established manufacturer email\nand export paths. This inbound operation never downloads remote artwork,\ncalls the integration callback, or emits the product-created webhook.\nRequires `products:write`.\n\n`integration_id` must identify an enabled custom integration belonging to\nthe Store. `store_product_id` is the integration's stable external id. This\nv3 operation rejects an id already present in the Store and serialises\nconcurrent v3 synchronisations. During migration, do not send the same\nlogical product through legacy and v3 writes in parallel. Money is integer\nminor units and `currency` must equal the Store currency. Artwork\nreferences must be credential-free HTTPS URLs no longer than 255\ncharacters; they are stored as references and are not fetched during this\nrequest.\n\nSend a unique `Idempotency-Key` for each logical product. Repeating the\nexact method, path, body, credential, and key replays the original 201\nresponse with `Idempotency-Replay: true` and does not create another\nproduct. A 409 `idempotency_key_in_flight` is safe to retry with the same\nkey after `retry_after`; a key/body conflict needs a new key or the original\nbody. Correct every pointer reported by a 422 before retrying.","operationId":"createProduct","parameters":[{"in":"header","name":"Idempotency-Key","example":"8cde76ce-10e9-4fc2-9d31-93af24277e9f","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"customiser_id":{"type":"integer"},"title":{"$ref":"#/components/schemas/NullableString"},"price_amount":{"type":"integer"},"compare_at_amount":{"$ref":"#/components/schemas/NullableInteger"},"currency":{"$ref":"#/components/schemas/NullableString"},"image_url":{"$ref":"#/components/schemas/NullableString"},"created_at":{"$ref":"#/components/schemas/NullableString"},"updated_at":{"$ref":"#/components/schemas/NullableString"},"description":{"$ref":"#/components/schemas/NullableString"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"},"artwork":{"type":["object","null"],"example":null}},"required":["object","id","customiser_id","title","price_amount","compare_at_amount","currency","image_url","created_at","updated_at","description","width_cm","height_cm","length_cm","artwork"]},"links":{"$ref":"#/components/schemas/CustomiserProductLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","links","meta"]}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown or foreign resource","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"External product already exists","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Integration does not accept writes","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Products"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"integration_id":{"type":"integer","description":"Enabled custom integration id belonging to the Store.","example":17},"store_product_id":{"type":"string","description":"Stable product id in the integration.","example":"ext-product-1001"},"title":{"type":"string","description":"Product title.","example":"Custom neon sign"},"description":{"$ref":"#/components/schemas/NullableString"},"price_amount":{"type":"integer","description":"Unit price in minor currency units.","example":12900},"compare_at_amount":{"$ref":"#/components/schemas/NullableInteger"},"currency":{"type":"string","description":"Uppercase ISO 4217 Store currency.","example":"AUD"},"width_cm":{"$ref":"#/components/schemas/NullableNumber"},"height_cm":{"$ref":"#/components/schemas/NullableNumber"},"length_cm":{"$ref":"#/components/schemas/NullableNumber"},"customisations":{"type":["array","null"],"description":"Nullable canonical customer selections; keys must be unique snake_case strings.","example":[{"key":"sign_text","label":"Sign text","value":"Hello"}],"items":{"type":"object","properties":{"key":{"type":"string","description":"Stable snake_case selection key. `size_cm` and `size_in` cannot be combined with first-class dimensions.","example":"sign_text"},"label":{"type":"string","description":"Human-readable selection label.","example":"Sign text"},"value":{"type":"string","description":"Captured selection value.","example":"Hello"}},"required":["key","label","value"],"additionalProperties":false}},"artwork":{"type":["object","null"],"description":"Nullable artwork references.","example":{"preview_image_url":"https://cdn.example.com/sign.png","svg_url":"https://cdn.example.com/sign.svg"},"properties":{"preview_image_url":{"$ref":"#/components/schemas/NullableString"},"outline_image_url":{"$ref":"#/components/schemas/NullableString"},"svg_url":{"$ref":"#/components/schemas/NullableString"},"eps_url":{"$ref":"#/components/schemas/NullableString"},"illustrator_pdf_url":{"$ref":"#/components/schemas/NullableString"},"dxf_url":{"$ref":"#/components/schemas/NullableString"},"custom_background_url":{"$ref":"#/components/schemas/NullableString"},"custom_background_original_url":{"$ref":"#/components/schemas/NullableString"},"source_file_url":{"$ref":"#/components/schemas/NullableString"}},"additionalProperties":false}},"required":["integration_id","store_product_id","title","price_amount","currency"],"additionalProperties":false}}}},"security":[{"default":["products:write"]}]}},"/api/v3/quotes":{"parameters":[],"get":{"summary":"List quotes\n\nLists the authenticated Store's quote requests and custom-design form\nsubmissions, newest first. Requires `quotes:read`, which is granted\nseparately from `customisers:read` because submissions carry end-customer\npersonal data. Follow `links.next` while `pagination.has_more` is true,\nsending each cursor back unchanged; changing any filter invalidates an\noutstanding cursor with a 422 `invalid_cursor` problem, so restart the\nwalk from the first page after a filter change. The operation is read-only\nand always safe to retry.\n\nDeduplicate on `id`, which is the submission's immutable identifier and\nstable across reads. Never deduplicate on `submission_number`: it counts\nsubmissions within one form, so every form has its own submission 1 and\nthe number repeats across a store. The `form:submitted` webhook does not\ncarry `id`, so a delivery cannot yet be matched to a row here by\nidentifier.\n\nSubmissions whose owning store could not be proven are excluded. That is\nexpected, not an error: it covers rows whose form was hard-deleted before\nthe platform began recording deletion snapshots, and no read can recover\nthem.","operationId":"listQuotes","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}},{"in":"query","name":"filter[type]","description":"Restrict to one kind of submission. One of quote, custom_design.","example":"quote","required":false,"schema":{"type":"string","description":"Restrict to one kind of submission. One of quote, custom_design."}},{"in":"query","name":"filter[customiser_id]","description":"Restrict to submissions collected by one customiser.","example":42,"required":false,"schema":{"type":"integer","description":"Restrict to submissions collected by one customiser."}},{"in":"query","name":"filter[form_id]","description":"Restrict to submissions of one form.","example":7,"required":false,"schema":{"type":"integer","description":"Restrict to submissions of one form."}},{"in":"query","name":"filter[created_from]","description":"Earliest UTC day to include, inclusive, as YYYY-MM-DD.","example":"2026-07-01","required":false,"schema":{"type":"string","description":"Earliest UTC day to include, inclusive, as YYYY-MM-DD."}},{"in":"query","name":"filter[created_to]","description":"Latest UTC day to include, inclusive, as YYYY-MM-DD. Must not be earlier than filter[created_from].","example":"2026-07-31","required":false,"schema":{"type":"string","description":"Latest UTC day to include, inclusive, as YYYY-MM-DD. Must not be earlier than filter[created_from]."}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"type":{"$ref":"#/components/schemas/CustomiserFormFormType"},"submission_number":{"type":"integer","description":"The form's own display counter for this submission. Not unique across forms and not a deduplication key; use id."},"customiser_id":{"type":"integer"},"form_id":{"type":"integer"},"responses":{"type":"array","description":"The shopper's answers, labelled and typed as the field was when they answered. Answers to payload keys no field asked for are dropped.","items":{"type":"object","properties":{"field_id":{"type":"string"},"label":{"type":"string"},"input_type":{"$ref":"#/components/schemas/CustomiserFormFieldInputType"},"value":{"type":"string","description":"The answer as text. A multi-value answer is comma-joined so one field's type cannot change the response shape."}},"required":["input_type","value"]}},"files":{"type":"array","items":{"type":"object","properties":{"field_id":{"type":"string"},"file_index":{"type":"integer"},"kind":{"$ref":"#/components/schemas/QuoteItemFileKind"},"original_filename":{"type":"string","description":"The name the shopper's file had. Submissions that predate filename capture fall back to the name in the stored URL, and null when even that is unusable."},"url":{"type":"string","description":"A durable public URL on a platform-owned host, never the merchant storefront. Durability is not a compatibility guarantee: a future move behind a private storage boundary may replace these URLs while the field shape stays."}},"required":["kind","original_filename","url"]}},"design":{"type":"object","properties":{"text":{"type":"string"},"text_align":{"type":"string"},"text_orientation":{"type":"string"},"font":{"type":"string"},"colour":{"type":"string"},"backlight":{"$ref":"#/components/schemas/NullableString"},"material":{"type":"string"},"letter_type":{"type":"string"},"support":{"type":"string"},"support_finish":{"type":"string"},"jacket":{"$ref":"#/components/schemas/NullableString"},"mounting":{"type":"string"},"mounting_colour":{"$ref":"#/components/schemas/NullableString"},"size_display":{"type":"string"},"final_dimensions":{"type":"string"},"quoted_price_amount":{"type":"integer","description":"The quoted total in minor units of currency. Sign Customiser quoted it at submission time; it is not a committed price."},"quoted_price_display":{"type":"string"},"currency":{"type":"string"}},"required":["quoted_price_amount"],"description":"The sign the shopper had configured, on quote submissions only; null on custom_design submissions and on quote submissions that stored no design data. Values are the labels the shopper saw, because the options are merchant-defined."},"ad_click_ids":{"$ref":"#/components/schemas/QuoteItemAdClickIds"},"utm_params":{"$ref":"#/components/schemas/QuoteItemUtmParams"},"created_at":{"type":"string"}},"required":["type","submission_number","responses","design"]}},"pagination":{"$ref":"#/components/schemas/CustomiserPrototypePagination"},"links":{"$ref":"#/components/schemas/CustomiserPrototypeLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unsupported filter value","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/AnalyticsSummaryResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Quotes"],"security":[{"default":["quotes:read"]}]}},"/api/v3/quotes/{quote_id}":{"parameters":[{"in":"path","name":"quote_id","description":"The submission id, from the list quotes operation.","example":"8123","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Retrieve a quote\n\nReturns one quote request or custom-design submission belonging to the\nauthenticated Store. Requires `quotes:read`. The id is the submission's\nimmutable identifier, as returned by the list operation.\n\nAn id that belongs to another store, an id that never existed, and an id\nwhose owning store the platform cannot prove all return the same 404, so\na caller cannot use this operation to discover another store's data. The\noperation is read-only and always safe to retry.","operationId":"getQuote","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"type":{"$ref":"#/components/schemas/CustomiserFormFormType"},"submission_number":{"type":"integer"},"customiser_id":{"type":"integer"},"form_id":{"type":"integer"},"responses":{"type":"array","items":{"type":"object","properties":{"field_id":{"type":"string"},"label":{"type":"string"},"input_type":{"$ref":"#/components/schemas/CustomiserFormFieldInputType"},"value":{"type":"string"}},"required":["input_type"]}},"files":{"type":"array","items":{"type":"object","properties":{"field_id":{"type":"string"},"file_index":{"type":"integer"},"kind":{"$ref":"#/components/schemas/QuoteItemFileKind"},"original_filename":{"type":"string"},"url":{"type":"string"}},"required":["kind"]}},"design":{"type":"object","properties":{"text":{"type":"string"},"text_align":{"type":"string"},"text_orientation":{"type":"string"},"font":{"type":"string"},"colour":{"type":"string"},"backlight":{"$ref":"#/components/schemas/NullableString"},"material":{"type":"string"},"letter_type":{"type":"string"},"support":{"type":"string"},"support_finish":{"type":"string"},"jacket":{"$ref":"#/components/schemas/NullableString"},"mounting":{"type":"string"},"mounting_colour":{"$ref":"#/components/schemas/NullableString"},"size_display":{"type":"string"},"final_dimensions":{"type":"string"},"quoted_price_amount":{"type":"integer"},"quoted_price_display":{"type":"string"},"currency":{"type":"string"}},"description":"The sign the shopper had configured, on quote submissions only; null on custom_design submissions and on quote submissions that stored no design data. Values are the labels the shopper saw, because the options are merchant-defined."},"ad_click_ids":{"$ref":"#/components/schemas/QuoteItemAdClickIds"},"utm_params":{"$ref":"#/components/schemas/QuoteItemUtmParams"},"created_at":{"type":"string"}},"required":["type","design"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown quote","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Quotes"],"security":[{"default":["quotes:read"]}]}},"/api/v3/stores/me/subscription":{"parameters":[],"get":{"summary":"Get the Store subscription\n\nReturn the authenticated Store's current local subscription projection.\nThe same provider-neutral contract covers Universal and Shopify Stores,\nwith a stable machine `plan`, separate display name, billing interval,\nrenewal intent, and known trial or end timestamps. Status is expected to\ngrow. Provider ids, payment details, test flags, customer details, and raw\nprovider objects are never exposed or fetched. Requires `store:read`; this\noperation is read-only and safe to retry.\n\nThis endpoint accepts no query parameters. A 401 means the key is missing\nor invalid. A 403 means it lacks `store:read`; create a correctly scoped\nkey. Remove parameters after a 422. Retry a 429 after `retry_after`\nseconds.","operationId":"getStoreSubscription","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"object":"subscription","status":"active","plan":"seed","plan_display_name":"Seed","billing_interval":"monthly","recurring":true,"trial_ends_at":null,"ends_at":null},"links":{"self":"https://web.signcustomiser.com/api/v3/stores/me/subscription","documentation":"https://www.signcustomiser.com/help/api/"},"meta":{"api_version":"v3","request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"}},"properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"status":{"type":"string","description":"Current provider-neutral subscription status. This set is expected to grow; tolerate unknown values.","enum":["none","trialing","active","past_due","incomplete","unpaid","cancelling","cancelled","unknown"]},"plan":{"$ref":"#/components/schemas/NullableString"},"plan_display_name":{"$ref":"#/components/schemas/NullableString"},"billing_interval":{"type":["string","null"],"description":"Closed billing interval when a local Plan is known.","enum":["monthly","yearly",null]},"recurring":{"type":"boolean"},"trial_ends_at":{"$ref":"#/components/schemas/NullableString"},"ends_at":{"$ref":"#/components/schemas/NullableString"}},"required":["object","status","plan","plan_display_name","billing_interval","recurring","trial_ends_at","ends_at"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Store"],"security":[{"default":["store:read"]}]}},"/api/v3/stores/me":{"parameters":[],"get":{"summary":"Get the authenticated store\n\nIdentify the store your API key belongs to. Call this first: it confirms\nthe key works, tells you which store you are operating on, whether the\nstore runs on the `shopify` or `universal` platform, its current\nsubscription plan, the ISO 4217 `currency` every price on the store is\ndenominated in, the commerce `integrations` it has connected, and the\nscopes your key grants — so you know which operations are available\nbefore calling them. The operation is read-only, has no side effects,\nand is always safe to retry. Requires the `store:read` scope.\n\nTwo of those fields are prerequisites for writing. Creating an order\n(`POST /api/v3/orders`) or an integration product\n(`POST /api/v3/customisers/{customiser_id}/products`) requires a\n`currency` equal to this store's and an `integration_id` naming a row\nwhere `accepts_writes` is true; this operation is where both values come\nfrom. Only an enabled custom integration accepts these writes. A store\nwhose `currency` is\n`null` has no valid three-letter currency configured and those writes\nfail with a 409 `store_currency_unavailable` until the merchant sets one\nin the merchant admin. A store with no enabled `custom` integration\ncannot create orders or integration products at all: the merchant\nconnects one in the merchant admin under Integrate.\n\nAuthenticate every request with a store API key sent as a bearer token\n(`Authorization: Bearer <api key>`); keys are created in the merchant\nadmin under Integrate → API Tokens with a chosen set of scopes and an\noptional expiry. A 401 problem response means the key is missing,\ninvalid, or expired — create or re-copy the key. A 403 problem response\nwith code `insufficient_scope` means the key is valid but lacks a\nrequired scope: the problem's `required_scopes` and `granted_scopes`\nmembers say which scope is missing and what the key can do; create a\nkey that includes the required scope. A 429 problem response means too\nmany requests: wait `retry_after` seconds, then retry. Error responses\nuse RFC 9457 problem+json: machine-readable `code`, a documentation\nlink in `type`, and a `request_id` to quote when contacting support.","operationId":"getStore","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"store\" for this endpoint (closed set).","enum":["store"]},"id":{"type":"integer"},"name":{"type":"string"},"platform":{"type":"string","description":"The platform the store runs on (closed set).","enum":["shopify","universal"]},"plan":{"$ref":"#/components/schemas/NullableString"},"currency":{"$ref":"#/components/schemas/NullableString"},"integrations":{"type":"array","description":"The commerce integrations connected to this store, enabled ones first, then oldest first. At most 50 are returned. Select a row where accepts_writes is true before creating an integration product or order. An empty array means the merchant has connected nothing.","items":{"type":"object","properties":{"object":{"type":"string","description":"The resource type discriminator. Always \"integration\" for these items (closed set).","enum":["integration"]},"id":{"type":"integer","description":"The integration id. This is the value order and integration product writes send as integration_id; those writes accept it only while the integration has type custom and enabled true."},"type":{"type":"string","description":"The connector this integration talks to, from the current set: custom, shopify, woocommerce, wix, etsy. This set is expected to grow — tolerate unknown values. Only an enabled custom integration accepts order and integration product writes."},"name":{"type":"string","description":"The name the merchant gave this integration in the merchant admin."},"enabled":{"type":"boolean","description":"Whether the integration is currently enabled. A disabled integration remains listed so the caller can tell the merchant to enable it."},"accepts_writes":{"type":"boolean","description":"Whether order and integration product writes accept this integration. This is true only for an enabled custom integration."}},"required":["object","id","type","name","enabled","accepts_writes"]}},"scopes":{"$ref":"#/components/schemas/OrderProducts"},"created_at":{"type":"string"}},"required":["object","platform","plan","currency","integrations","scopes"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CustomiserOneOf2"},{"$ref":"#/components/schemas/CustomiserOneOf2"},{"$ref":"#/components/schemas/CustomiserOneOf2"}],"required":["type","title","status","code","detail","request_id"]}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown query parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserResponse2"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Stores"],"security":[{"default":["store:read"]}]}},"/api/v3/uploads":{"parameters":[],"post":{"summary":"Create an upload\n\nDeclares a file and returns a short-lived presigned target to send its\nbytes to. Requires `customisers:write` and an `Idempotency-Key`.\n\nThe `purpose` selects the accepted file types and the size cap, so\ndeclare the purpose of the slot you intend to attach to. Both the\nfilename extension and the content type must be ones the purpose accepts;\na mismatch answers 422 with the accepted values, and an oversized\ndeclaration answers 422 with `max_byte_size`.\n\n`backboard_shape_svg` is the one purpose that behaves differently once\nattached. It is the only purpose on this API that accepts SVG, every\ndocument is sanitised server-side before it is stored, and the result is\nheld inside the backboard rather than published at a URL. It attaches at\n`PUT /customisers/{customiser_id}/backboards/{backboard_id}/shape`.\n\nSend the bytes with the `upload_target`: use its `method`, its `url`, and\nevery header it lists, with the file as the raw request body. The target\nexpires about ten minutes after it is issued and is returned only by this\nresponse, so do not cache or store it. An idempotent replay of this\nrequest returns the recorded body with `upload_target` set to null, which\nmeans a retry after the target expired needs a new upload rather than the\nsame one. Poll `GET /api/v3/uploads/{upload_id}` to confirm the bytes\nlanded.\n\nNothing is verified here. The declared size and type are what the API\nwill issue a target for; the actual bytes are checked when you attach the\nupload to a file slot, and a file that fails that check is refused then.","operationId":"createUpload","parameters":[{"in":"header","name":"Idempotency-Key","example":"0f2b1c34-7a5e-4d21-9c6b-8e4f0a13d572","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"purpose":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer"},"status":{"type":"string","description":"Closed set. `pending` until the bytes are observed in staging, then `uploaded`, then `attached` once a file slot takes it. `rejected` is terminal and `expired` means the upload can no longer be used.","enum":["pending","uploaded","attached","rejected","expired"]},"verification_failure_code":{"$ref":"#/components/schemas/NullableString"},"expires_at":{"type":"string"},"attached_at":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"upload_target":{"type":"object","properties":{"method":{"type":"string"},"url":{"type":"string"},"headers":{"$ref":"#/components/schemas/CustomiserProductCart"},"expires_at":{"type":"string"}},"description":"Where to send the bytes: send the file as the raw body of a request using `method` to `url`, with every header in `headers` set exactly as given (the set depends on the storage provider and is often empty). Present only on this response — an idempotent replay returns it as null, and reads never include it. The example above is synthetic and does not accept uploads."}},"required":["status","upload_target"]},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"File type not allowed","content":{"application/problem+json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/BackboardImageErrors"},"purpose":{"type":"string"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Upload target budget exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Uploads"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/uploads/validate-create":{"parameters":[],"post":{"summary":"Validate an upload create\n\nDry-runs `POST /api/v3/uploads` with the same body. Returns the same\nproblem the create would return, or a verdict saying it would succeed.\nRequires `customisers:write`. Nothing is created, no target is minted, no\nidempotency key is consumed, and no budget is spent.","operationId":"validateCreateUpload","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"operation":{"type":"string"},"purpose":{"type":"string"},"valid":{"type":"boolean"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"File too large","content":{"application/problem+json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string"},"detail":{"type":"string"},"errors":{"$ref":"#/components/schemas/CustomiserOneOfErrors"},"purpose":{"type":"string"},"max_byte_size":{"type":"integer"},"request_id":{"type":"string"}},"required":["type","title","status","code","detail","request_id"]}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Uploads"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}},"security":[{"default":["customisers:write"]}]}},"/api/v3/uploads/{upload_id}":{"parameters":[{"in":"path","name":"upload_id","description":"The opaque upload ID.","example":"upl_9f2c4b7ad13e6058ba41cd","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get an upload\n\nReturns an upload's metadata and current status. Requires\n`customisers:read`. Uploads belonging to another store are indistinguishable\nfrom unknown ones.\n\n`status` is `pending` until the bytes arrive in staging and `uploaded`\nonce they have, so this is how you confirm a presigned PUT landed before\nattaching. The presigned target is never returned here: it exists only on\nthe create response.","operationId":"getUpload","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"purpose":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer"},"status":{"type":"string"},"verification_failure_code":{"$ref":"#/components/schemas/NullableString"},"expires_at":{"type":"string"},"attached_at":{"$ref":"#/components/schemas/NullableString"},"created_at":{"type":"string"},"upload_target":{"$ref":"#/components/schemas/NullableString"}}},"links":{"$ref":"#/components/schemas/AnalyticsSummaryLinks"},"meta":{"$ref":"#/components/schemas/AnalyticsSummaryMeta"}}}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Uploads"],"security":[{"default":["customisers:read"]}]}},"/api/v2/webhooks":{"parameters":[],"get":{"summary":"List all webhooks\n\nGet a list of all webhooks for the current organisation.","operationId":"listAllWebhooksGetAListOfAllWebhooksForTheCurrentOrganisation","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookWebhook"}}}}}}}},"tags":["Webhooks"],"deprecated":true},"post":{"summary":"Create a new webhook\n\nRegister a new webhook endpoint to receive event notifications.","operationId":"createANewWebhookRegisterANewWebhookEndpointToReceiveEventNotifications","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}}},"tags":["Webhooks"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRequest"}}}}}},"/api/v2/webhooks/{webhook_id}":{"parameters":[{"in":"path","name":"webhook_id","description":"The ID of the webhook.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a single webhook\n\nRetrieve details for a specific webhook.","operationId":"getASingleWebhookRetrieveDetailsForASpecificWebhook","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}}},"tags":["Webhooks"],"deprecated":true},"put":{"summary":"Update a webhook\n\nUpdate an existing webhook's topic, URL, or metadata.","operationId":"updateAWebhookUpdateAnExistingWebhooksTopicURLOrMetadata","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}}},"tags":["Webhooks"],"deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRequest"}}}}},"delete":{"summary":"Delete a webhook\n\nRemove a webhook subscription.","operationId":"deleteAWebhookRemoveAWebhookSubscription","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}},"tags":["Webhooks"],"deprecated":true}},"/api/v2/webhook-examples/{topic}":{"parameters":[{"in":"path","name":"topic","description":"The webhook topic to get examples for. Must be one of: product:created, order:created, form:submitted.","example":"product:created","required":true,"schema":{"type":"string"}}],"get":{"summary":"Webhook payload examples\n\nRetrieve example webhook payloads for a specific topic. If data exists for the current organisation, real data will be used. Otherwise, example data is returned.","operationId":"webhookPayloadExamplesRetrieveExampleWebhookPayloadsForASpecificTopicIfDataExistsForTheCurrentOrganisationRealDataWillBeUsedOtherwiseExampleDataIsReturned","responses":{"200":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"Product Created","type":"object","properties":{"product":{"type":"object","properties":{"product_id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"description_html":{"type":"string"},"external_id":{"type":"string"},"external_data":{"$ref":"#/components/schemas/CustomiserProductCart"},"price":{"type":"integer"},"compare_price":{"type":"integer"},"customiser_id":{"type":"integer"},"logo_upload_id":{"$ref":"#/components/schemas/NullableString"},"product_image_url":{"type":"string"},"uploads":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"type":{"type":"string"}}}},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}},{"description":"Order Created","type":"object","properties":{"order":{"type":"object","properties":{"order_id":{"type":"integer"},"order_number":{"type":"integer"},"external_order_number":{"type":"string"},"external_id":{"type":"string"},"order_total":{"type":"integer"},"app_total":{"type":"integer"},"currency":{"type":"string"},"shipping_line":{"type":"string"},"billing_address":{"$ref":"#/components/schemas/WebhookExampleOneOfOrderBillingAddress"},"shipping_address":{"$ref":"#/components/schemas/WebhookExampleOneOfOrderBillingAddress"},"customer":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"external_id":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"products":{"type":"array","items":{"type":"object","properties":{"product_image_url":{"type":"string"},"product_id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"description_html":{"type":"string"},"external_id":{"type":"string"},"external_data":{"type":"array"},"price":{"type":"integer"},"compare_price":{"type":"integer"},"customiser_id":{"type":"integer"},"logo_upload_id":{"$ref":"#/components/schemas/NullableString"},"uploads":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"path":{"type":"string"},"type":{"type":"string"}}}},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}},{"description":"Form Submitted","type":"object","properties":{"form_submission":{"type":"object","properties":{"submission_number":{"type":"integer"},"type":{"type":"string"},"form":{"type":"object","properties":{"form_id":{"type":"integer"},"customiser_id":{"type":"integer"},"label":{"type":"string"},"description":{"type":"string"},"email":{"type":"string"},"subject":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"responses":{"type":"array","items":{"type":"object","properties":{"field_id":{"type":"string"},"value":{"type":"string"},"name":{"type":"string"},"input_type":{"type":"string"}}}},"created_at":{"type":"string"}}}}}]}}}}},"tags":["Webhooks"],"deprecated":true}},"/api/v3/webhook-subscriptions":{"parameters":[],"get":{"summary":"List webhook subscriptions\n\nLists the authenticated Store's outbound webhook subscriptions in stable\ncreation order. Requires `webhooks:read`. Follow `links.next` while\n`pagination.has_more` is true. Signing secrets and provider metadata are\nnever returned.","operationId":"listWebhookSubscriptions","parameters":[{"in":"query","name":"limit","description":"Page size from 1 to 100. Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Page size from 1 to 100. Defaults to 20."}},{"in":"query","name":"cursor","description":"Opaque cursor from the previous page.","example":null,"required":false,"schema":{"type":"string","description":"Opaque cursor from the previous page.","example":null}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscriptionItem"}},"pagination":{"$ref":"#/components/schemas/CustomiserPagination"},"links":{"$ref":"#/components/schemas/WebhookEventTypeLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","pagination","links","meta"]}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Invalid query","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"security":[{"default":["webhooks:read"]}]},"post":{"summary":"Create a webhook subscription\n\nRegisters an HTTPS callback for one topic. Requires `webhooks:write` and\nan `Idempotency-Key`. The response contains the signing `secret`; store\nit securely because later subscription responses never return it. Verify\ndeliveries using https://www.signcustomiser.com/help/integrations/subscribe-to-webhooks/.\nRetrying the exact request and key replays the original response without\ncreating a second subscription or rotating the secret. Registration does\nnot itself enable delivery: `product:created` and `order:created` emit only\nfor Stores with custom API access. Contact Sign Customiser support to\nconfirm eligibility if those events do not arrive.","operationId":"createWebhookSubscription","parameters":[{"in":"header","name":"Idempotency-Key","example":"9a56c1eb-2812-4b75-9ee2-79d690c67968","schema":{"type":"string"},"required":true}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"integer"},"topic":{"$ref":"#/components/schemas/WebhookEventTypeItemTopic"},"url":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"secret":{"description":"The show-once signing credential. Store it securely; it is returned only by create and an exact idempotent replay.","type":"string"}},"required":["object","id","topic","url","created_at","updated_at","secret"]},"links":{"$ref":"#/components/schemas/CustomiserProductLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","links","meta"]}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"409":{"description":"Idempotency conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unsafe callback","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"topic":{"$ref":"#/components/schemas/WebhookEventTypeItemTopic"},"url":{"type":"string","description":"Public HTTPS callback URL, at most 255 characters.","example":"https://example.com/webhooks/sign-customiser"}},"required":["topic","url"],"additionalProperties":false}}}},"security":[{"default":["webhooks:write"]}]}},"/api/v3/webhook-subscriptions/{webhook_id}":{"parameters":[{"in":"path","name":"webhook_id","description":"The canonical subscription ID.","example":"1","required":true,"schema":{"type":"integer"}}],"get":{"summary":"Get a webhook subscription\n\nReturns one Store-scoped subscription without its signing secret or\nprovider metadata. Requires `webhooks:read` and is safe to retry.","operationId":"getWebhookSubscription","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown subscription","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"security":[{"default":["webhooks:read"]}]},"patch":{"summary":"Update a webhook subscription\n\nApplies an RFC 7396 JSON Merge Patch to `topic` and/or `url`. Omitted\nfields remain unchanged; null, read-only, and unknown fields are rejected.\nRequires `webhooks:write` and an `Idempotency-Key`.","operationId":"updateWebhookSubscription","parameters":[{"in":"header","name":"Idempotency-Key","example":"2044fd1e-2812-430f-9ae5-e6df09a8b40f","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown subscription","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Provider managed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"413":{"description":"Request body too large","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Invalid merge patch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/CustomiserValidateCreateResponse"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"topic":{"$ref":"#/components/schemas/WebhookEventTypeItemTopic"},"url":{"type":"string","description":"Public HTTPS callback URL, at most 255 characters.","example":"https://example.com/webhooks/sign-customiser-v2"}},"additionalProperties":false}}}},"security":[{"default":["webhooks:write"]}]},"delete":{"summary":"Delete a webhook subscription\n\nDeletes a directly managed subscription. Requires `webhooks:write` and\nan `Idempotency-Key`. Retrying the same request and key replays the\noriginal success after the row is gone. Provider-managed subscriptions\nreturn 409 and must be removed through their provider.","operationId":"deleteWebhookSubscription","parameters":[{"in":"header","name":"Idempotency-Key","example":"2dcf1bad-2812-4742-a968-9ecce02c05fe","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionResponse"}}}},"400":{"description":"Missing idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"404":{"description":"Unknown subscription","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"409":{"description":"Provider managed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"security":[{"default":["webhooks:write"]}]}},"/api/v3/webhook-event-types":{"parameters":[],"get":{"summary":"List webhook event types\n\nLists every event topic available for subscription, a static example of\nthe payload currently delivered for that topic, and the existing signing\nverification guide. Requires `webhooks:read`. The catalogue is one\ncomplete page, performs no Store-data reads, and accepts no query\nparameters. Topic values are expected to grow; clients must tolerate\nunknown future values. `product:created` and `order:created` emit only for\nStores with custom API access; catalogue visibility and successful\nsubscription registration do not establish delivery eligibility. Contact\nSign Customiser support to confirm access if those events do not arrive.\nA topic with `deprecated: true` still fires with an unchanged payload; its\ndescription names the replacement operation and the scope that operation\nrequires.","operationId":"listWebhookEventTypes","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"object":"webhook_event_type","topic":"product:created","description":"Sent after Sign Customiser creates a customer-designed product.","deprecated":false,"example_payload":{"product":{"product_image_url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","product_id":1,"title":"Custom Neon: Hello World","description":"Text: Hello World\nColour: Red\nFont: Comics","description_html":"<p>Text: Hello World</p>\n<p>Colour: Red</p>\n<p>Font: Comics</p>","external_id":"1234","external_data":[],"price":10000,"compare_price":15000,"customiser_id":1,"customiser_language_id":1,"customiser_language_code":"en","customiser_default_language_code":"en","customiser_languages_version":"example-version","customiser_language_hash":"example-hash","customer_display_snapshot":{"line_items":{"Text":"Hello World"},"fields":{"text":{"label":"Text","value":"Hello World"}}},"logo_upload_id":null,"uploads":[{"url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","path":"app-images/demonstration_image_1.jpg","type":"product_image"}],"logo_upload":null,"width_cm":10,"height_cm":10,"length_cm":10,"border_margin_cm":null,"mounting_letter_part":null,"created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00","letter_heights_cm":null}},"links":{"signature_verification":"https://www.signcustomiser.com/help/integrations/subscribe-to-webhooks/"}},{"object":"webhook_event_type","topic":"order:created","description":"Sent after Sign Customiser records an order containing customised products.","deprecated":false,"example_payload":{"order":{"order_id":1,"order_number":4,"external_order_number":"SIGN-12345","external_id":"12345","order_total":120000,"app_total":100000,"currency":"USD","shipping_line":"ground","billing_address":{"first_name":"John","last_name":"Doe","company":"Acme","address_1":"123 Fake Street","address_2":"Unit 456","city":"Brisbane","state":"Queensland","postcode":"4000","country":"Australia","phone":"123456789","email":"john@example.com","created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00"},"shipping_address":{"first_name":"John","last_name":"Doe","company":"Acme","address_1":"123 Fake Street","address_2":"Unit 456","city":"Brisbane","state":"Queensland","postcode":"4000","country":"Australia","phone":"123456789","email":"john@example.com","created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00"},"customer":{"first_name":"John","last_name":"Doe","phone":"123456789","email":"john@example.com","external_id":"1234","created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00"},"products":[{"product_image_url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","product_id":1,"title":"Custom Neon: Hello World","description":"Text: Hello World\nColour: Red\nFont: Comics","description_html":"<p>Text: Hello World</p>\n<p>Colour: Red</p>\n<p>Font: Comics</p>","external_id":"1234","external_data":[],"price":10000,"compare_price":15000,"customiser_id":1,"customiser_language_id":1,"customiser_language_code":"en","customiser_default_language_code":"en","customiser_languages_version":"example-version","customiser_language_hash":"example-hash","customer_display_snapshot":{"line_items":{"Text":"Hello World"},"fields":{"text":{"label":"Text","value":"Hello World"}}},"logo_upload_id":null,"uploads":[{"url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","path":"app-images/demonstration_image_1.jpg","type":"product_image"}],"logo_upload":null,"width_cm":10,"height_cm":10,"length_cm":10,"border_margin_cm":null,"mounting_letter_part":null,"created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00","letter_heights_cm":null}],"legacy_products":[],"created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00"}},"links":{"signature_verification":"https://www.signcustomiser.com/help/integrations/subscribe-to-webhooks/"}},{"object":"webhook_event_type","topic":"form:submitted","description":"Deprecated. Sent after a customer submits a Sign Customiser form. Read submissions from GET /api/v3/quotes with the quotes:read scope instead, and deduplicate on the quote id. This webhook still fires on every new submission with an unchanged payload, and has no removal date.","deprecated":true,"example_payload":{"form_submission":{"submission_number":1234,"type":"custom_form_submission","form":{"form_id":1,"customiser_id":1,"label":"Custom Design Form","description":"Lorem ipsum dolor","email":"test@test.com","subject":"My Custom Email Subject","created_at":"2024-01-01T00:00:00+00:00","updated_at":"2024-01-01T00:00:00+00:00"},"responses":[{"field_id":"abcd1234","value":"test@test.com","name":"Your Email","input_type":"EMAIL"}],"created_at":"2024-01-01T00:00:00+00:00","ad_click_ids":{"gclid":"CjwKCAjw7p6aBhAoBhAAEiwA..."},"utm_params":{"utm_source":"google","utm_medium":"cpc","utm_campaign":"spring_sale"}}},"links":{"signature_verification":"https://www.signcustomiser.com/help/integrations/subscribe-to-webhooks/"}}],"pagination":{"has_more":false,"next_cursor":null},"links":{"self":"https://web.signcustomiser.com/api/v3/webhook-event-types","documentation":"https://www.signcustomiser.com/help/api/","next":null},"meta":{"api_version":"v3","request_id":"req_01jz9x2k7c8f3m5n6p7q8r9s0t"}},"properties":{"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"topic":{"$ref":"#/components/schemas/WebhookEventTypeItemTopic"},"description":{"type":"string"},"deprecated":{"type":"boolean","description":"Whether new integrations should build against this topic. A deprecated topic keeps firing with an unchanged payload and has no removal date unless its description names one."},"example_payload":{"additionalProperties":true,"description":"A complete example of the payload delivered for this topic. Its shape depends on the topic.","type":"object"},"links":{"type":"object","properties":{"signature_verification":{"type":"string","example":"https://www.signcustomiser.com/help/integrations/subscribe-to-webhooks/"}},"required":["signature_verification"]}},"required":["object","topic","description","deprecated","example_payload","links"]}},"pagination":{"$ref":"#/components/schemas/CustomiserPagination"},"links":{"$ref":"#/components/schemas/WebhookEventTypeLinks"},"meta":{"$ref":"#/components/schemas/CustomiserProductMeta"}},"required":["data","pagination","links","meta"]}}}},"401":{"description":"Missing API key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InsufficientScopeProblem2"}}}},"422":{"description":"Unknown parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/MissingApiKeyProblem"}}}},"429":{"description":"Rate limited","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitedProblem"}}}}},"tags":["Webhook subscriptions"],"security":[{"default":["webhooks:read"]}]}}},"jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","webhooks":{"productCreated":{"post":{"description":"Use this as the product creation callback for custom integrations. When a customer finishes a design, Sign Customiser POSTs this payload to the create-product URL configured on your custom integration. Verify the signature, create the product in your own platform, then return a 200 JSON response containing the `external_id` you will later send in `POST /api/v2/orders`.","operationId":"receiveProductCreated","parameters":[{"description":"Unix timestamp used when generating the delivery signature.","example":"1704067200","in":"header","name":"x-webhook-timestamp","required":true,"schema":{"type":"string"}},{"description":"SHA-256 HMAC signature for the product:created payload.","example":"YOUR_GENERATED_SIGNATURE","in":"header","name":"x-webhook-signature","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"product":{"product_id":1,"title":"Custom Neon: Hello World","description":"Text: Hello World\nColour: Red\nFont: Comics","description_html":"<p>Text: Hello World</p>\n<p>Colour: Red</p>\n<p>Font: Comics</p>","external_id":"1234","external_data":{},"price":10000,"compare_price":15000,"customiser_id":1,"logo_upload_id":null,"product_image_url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","uploads":[{"url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","type":"product_image"}],"created_at":"2024-01-01T00:00:00.000000Z","updated_at":"2024-01-01T00:00:00.000000Z"}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Return your platform product ID","content":{"application/json":{"example":{"external_id":"your-product-id-123"},"schema":{"type":"object"}}}}},"summary":"Product created"}},"orderCreated":{"post":{"description":"Delivered when Sign Customiser records a completed order for your organisation.","operationId":"receiveOrderCreated","parameters":[{"description":"Unix timestamp used when generating the delivery signature.","example":"1704067200","in":"header","name":"x-webhook-timestamp","required":true,"schema":{"type":"string"}},{"description":"SHA-256 HMAC signature for the order:created payload.","example":"YOUR_GENERATED_SIGNATURE","in":"header","name":"x-webhook-signature","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"order":{"order_id":1,"order_number":4,"external_order_number":"SIGN-12345","external_id":"12345","order_total":120000,"app_total":100000,"currency":"USD","shipping_line":"ground","billing_address":{"first_name":"John","last_name":"Doe","company":"Acme","address_1":"123 Fake Street","address_2":"Unit 456","city":"Brisbane","state":"Queensland","postcode":"4000","country":"Australia","phone":"123456789","email":"john@example.com","created_at":"2025-10-22T00:55:09+00:00","updated_at":"2025-10-22T00:55:09+00:00"},"shipping_address":{"first_name":"John","last_name":"Doe","company":"Acme","address_1":"123 Fake Street","address_2":"Unit 456","city":"Brisbane","state":"Queensland","postcode":"4000","country":"Australia","phone":"123456789","email":"john@example.com","created_at":"2025-10-22T00:55:09+00:00","updated_at":"2025-10-22T00:55:09+00:00"},"customer":{"first_name":"John","last_name":"Doe","phone":"123456789","email":"john@example.com","external_id":"1234","created_at":"2025-10-22T00:55:09+00:00","updated_at":"2025-10-22T00:55:09+00:00"},"products":[{"product_image_url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","product_id":1,"title":"Custom Neon: Hello World","description":"Text: Hello World\nColour: Red\nFont: Comics","description_html":"<p>Text: Hello World</p>\n<p>Colour: Red</p>\n<p>Font: Comics</p>","external_id":"1234","external_data":[],"price":10000,"compare_price":15000,"customiser_id":1,"logo_upload_id":null,"uploads":[{"url":"https://d1no4rdxmwcuog.cloudfront.net/default/app-images/demonstration_image_1.jpg","path":"app-images/demonstration_image_1.jpg","type":"product_image"}],"created_at":"2025-10-22T00:55:09+00:00","updated_at":"2025-10-22T00:55:09+00:00"}],"created_at":"2025-10-22T00:55:09+00:00","updated_at":"2025-10-22T00:55:09+00:00"}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Acknowledge delivery","content":{"application/json":{"example":{"ok":true},"schema":{"type":"object"}}}}},"summary":"Order created"}},"formSubmitted":{"post":{"description":"Deprecated. Delivered when a customer submits a Sign Customiser form workflow. Read submissions from GET /api/v3/quotes with the quotes:read scope instead, and deduplicate on the quote id. This webhook still fires on every submission with an unchanged payload, and has no removal date.","operationId":"receiveFormSubmitted","parameters":[{"description":"Unix timestamp used when generating the delivery signature.","example":"1704067200","in":"header","name":"x-webhook-timestamp","required":true,"schema":{"type":"string"}},{"description":"SHA-256 HMAC signature for the form:submitted payload.","example":"YOUR_GENERATED_SIGNATURE","in":"header","name":"x-webhook-signature","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"form_submission":{"submission_number":1234,"type":"custom_form_submission","form":{"form_id":1,"customiser_id":1,"label":"Custom Design Form","description":"Lorem ipsum dolor","email":"test@test.com","subject":"My Custom Email Subject","created_at":"2024-01-01T00:00:00.000000Z","updated_at":"2024-01-01T00:00:00.000000Z"},"responses":[{"field_id":"abcd1234","value":"test@test.com","name":"Your Email","input_type":"EMAIL"}],"created_at":"2024-01-01T00:00:00.000000Z"}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"description":"Acknowledge delivery","content":{"application/json":{"example":{"ok":true},"schema":{"type":"object"}}}}},"summary":"Form submitted"}}}}
