Packages

Duplicate a package

Creates a copy of an existing Package with new fields applied from the request body. Useful when seeding a new offering off an existing one rather than building from scratch. The new package is created under the same site and organisation as the original; fields not listed in the body are inherited from the source unchanged.

Requires the SETTINGS_MANAGE permission on the original package's site. As with create/update, separate_couples_package_choices is forced to false unless both min_guests and max_guests equal 2, and overnight_package_config.enabled=true requires rate_id and num_nights.

post/shop/packages/{packageId}/copy

Request body

namestring

Display name for the new copy. Strongly recommended so the copy is distinguishable from the source.

descriptionstring

Override the customer-facing long-form description on the copy. Defaults to the source's description when omitted. Markdown rendered.

external_idstring nullable

Override external identifier on the copy. Defaults to null so integrations don't accidentally see two offerings with the same external id.

priceinteger

Flat base price (in the smallest currency unit, e.g. pence/cents) to set on the copy. Most packages prefer price_rules; this field is a convenience for the simple case.

standard_priceinteger nullable

Override the standard price on the copy, in the smallest currency unit (e.g. pence). Defaults to the source's standard price when omitted.

currencystring

The ISO-4217 currency code.

offered_onlineboolean

Whether the copy is bookable via the public shopfront. Defaults to the source's value.

privateboolean

When true, only customers with the direct link can book the copy.

visibility'public' | 'link_only' | 'private'

Visibility override on the copy. Same semantics as on the source.

customers_onlyboolean

When true, only registered customers (not guests) can book the copy.

members_onlyboolean

When true, only members of permitted membership types can book the copy.

permitted_membership_type_idsstring[] nullable

Membership types whose members can book the copy when members_only=true.

membership_booking_windows_enabledboolean

Whether to apply per-membership-type advance-booking windows on the copy.

customer_cancellation_permitted'allowed' | 'if_unpaid' | 'disallowed' nullable

Customer self-cancellation policy on the copy. allowed = always; if_unpaid = only if no payment has been taken; disallowed = never.

customer_cancellation_min_durationstring nullable

ISO 8601 duration before the package start during which cancellation is no longer permitted on the copy.

customer_reschedule_permitted'allowed' | 'disallowed' | 'unpaid' nullable

Whether and when customers can reschedule a booking of this type themselves. allowed = always. unpaid = only if no payment has been taken against the order. disallowed = never (admin only).

customer_reschedule_min_durationstring nullable

ISO 8601 duration before the package start during which rescheduling is no longer permitted on the copy.

max_daily_durationinteger

Maximum minutes between first-item start and last-item end on each day for the copy.

max_advance_bookings_intervalstring nullable

ISO 8601 duration limiting how far in advance customers can book the copy.

min_advance_bookings_intervalstring nullable

ISO 8601 duration limiting how soon before the package start a customer can book the copy.

min_guestsinteger nullable

Minimum guests per booking of the copy.

max_guestsinteger nullable

Maximum guests per booking of the copy.

prevent_choice_overlapsboolean

Whether to prevent booking choices in the copy from overlapping times.

separate_couples_package_choicesboolean nullable

For couples packages, whether each guest can independently pick choices on the copy. Forced to false for any other guest configuration.

include_pricing_on_calendarboolean

Whether to surface the price alongside the copy in calendar views.

max_per_basketinteger nullable

Maximum number of the copy per basket.

max_per_guestinteger nullable

Maximum number of the copy per individual guest per basket.

Example request

{
  "name": "Couples Spa Day (Premium)",
  "description": "An elevated couples day with extended treatments and\nprivate terrace access.\n",
  "external_id": "ext-couples-spa-day-premium",
  "price": 25000,
  "standard_price": 25000,
  "currency": "gbp",
  "availability_rules": [
    {
      "date_from": "2021-02-15",
      "date_to": "2021-02-15",
      "time_from": "11:00",
      "time_to": "17:00"
    }
  ],
  "membership_booking_windows": [
    {
      "max_advance_bookings_interval": "P2W",
      "min_advance_bookings_interval": "P2D"
    }
  ],
  "upsell_offerings": [
    {
      "offering_name": "Twilight massage",
      "offering_type": "appointment"
    }
  ],
  "cross_sell_offerings": [
    {
      "offering_name": "Twilight massage",
      "offering_type": "appointment"
    }
  ],
  "related_retail_offerings": [
    {
      "offering_name": "Twilight massage",
      "offering_type": "appointment"
    }
  ],
  "customer_cancellation_min_duration": "PT24H",
  "customer_reschedule_permitted": "allowed",
  "customer_reschedule_min_duration": "PT24H",
  "max_daily_duration": 240,
  "max_advance_bookings_interval": "P2W",
  "min_advance_bookings_interval": "P2D",
  "min_guests": 2,
  "max_guests": 2,
  "overnight_package_config": {
    "rate_id": "rate-king-bb",
    "num_nights": 1
  },
  "max_per_basket": 1,
  "max_per_guest": 1
}

Response

A single Package.

Example response

{
  "data": {
    "id": "5dcb47800000000000000010",
    "categories": [
      {
        "name": "Massages"
      }
    ],
    "cross_sell_offerings": [
      {
        "offering_name": "Twilight massage",
        "offering_type": "appointment"
      }
    ],
    "currency": "gbp",
    "customer_cancellation_min_duration": "P7D",
    "customer_cancellation_permitted": "allowed",
    "customer_reschedule_min_duration": "P7D",
    "customer_reschedule_permitted": "allowed",
    "description": "Buy together and save",
    "email_options": {
      "hide_prices": true
    },
    "image": {
      "file_name": "super-cool-photo.jpg",
      "mime_type": "image/jpeg",
      "original_url": "https://example.com/media/super-cool-photo.jpg",
      "size": 84256,
      "url": "https://example.com/media/super-cool-photo-thumbnail@2x.jpg"
    },
    "item_choices": [
      {
        "id": "5dcb47800000000000000025",
        "allow_overlaps": true,
        "auto_select_timeslot": true,
        "description": "Choose your type of massage.",
        "hide_times": true,
        "name": "Massage",
        "max_options": 2,
        "min_options": 1,
        "option_budget": 2000,
        "optional": true,
        "options": [
          {
            "id": "5dcb47800000000000000026",
            "item_type": "appointment",
            "offering": {
              "id": "5e932c0901d210625e3a8766",
              "categories": [
                {
                  "id": "5e932c0901d210625e3a8766",
                  "name": "Massage"
                }
              ],
              "currency": "gbp",
              "description": "Choose from classic, deep tissue and Swedish massage",
              "discounted_price_from": 4500,
              "duration": 30,
              "durations": [
                30
              ],
              "has_availability": true,
              "image": {
                "file_name": "super-cool-photo.jpg",
                "mime_type": "image/jpeg",
                "original_url": "https://example.com/media/super-cool-photo.jpg",
                "size": 84256,
                "url": "https://example.com/media/super-cool-photo-thumbnail@2x.jpg"
              },
              "max_guests": 2,
              "min_guests": 2,
              "name": "60 minute massage",
              "price_from": 5000,
              "price_to": 6500,
              "type": "appointment"
            },
            "price_change": 2000
          }
        ],
        "start_time_rules": [
          {
            "absolute_time_from": "12:00",
            "absolute_time_to": "12:30",
            "relative_mins_to": 30
          }
        ],
        "visible": true
      }
    ],
    "max_advance_bookings_interval": "P2W",
    "max_daily_duration": 240,
    "max_guests": 2,
    "max_per_basket": 1,
    "max_per_guest": 1,
    "members_only": true,
    "membership_booking_windows": [
      {
        "max_advance_bookings_interval": "P2W",
        "min_advance_bookings_interval": "P2D"
      }
    ],
    "membership_booking_windows_enabled": true,
    "min_advance_bookings_interval": "P2D",
    "min_guests": 2,
    "name": "Treatment and Yoga",
    "overnight_package_config": {
      "enabled": true,
      "rate_id": "5dcb47800000000000000010",
      "num_nights": 1
    },
    "prevent_choice_overlaps": true,
    "price_rules": [
      {
        "id": "5dcb47800000000000000010",
        "package_id": "5dcb47800000000000000010",
        "date_from": "2020-02-01",
        "date_to": "2020-04-01",
        "time_from": "10:00",
        "time_to": "14:00",
        "weekdays": [
          "monday"
        ],
        "price": 20000
      }
    ],
    "product_code": "MAS123",
    "related_retail_offerings": [
      {
        "offering_name": "Twilight massage",
        "offering_type": "appointment"
      }
    ],
    "site_id": "00000000-0000-0000-0000-111111111111",
    "standard_price": 5000,
    "upsell_offerings": [
      {
        "offering_name": "Twilight massage",
        "offering_type": "appointment"
      }
    ],
    "visibility": "public",
    "deleted_at": "2025-02-04T12:00:00+01:00",
    "updated_at": "2025-02-04T12:00:00+01:00"
  }
}

Changes