> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anny.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Booking Bundle Groups

> JSON:API contract for grouping booking bundles in admin and native apps. Mirror service groups and add-on groups.

Booking bundles (packages / codes) can be assigned to **booking bundle groups**, the same pattern as [service groups](https://docs.anny.co/developers/models/service-groups.md) and [add-on groups](https://docs.anny.co/developers/models/add-on-groups.md).

This is the contract for admin clients and native apps. For JSON:API conventions, see [JSON-API Conventions](https://docs.anny.co/developers/guides/json-api-conventions.md).

> [!NOTE]
>
> JSON:API type: `booking-bundle-groups`. Bundles expose a nullable `group` relationship (`booking-bundle-groups`). Deleting a group **detaches** bundles (`group_id` is set to `null`); it does not delete the bundles.

***

## Resource

| | |
| - | - |
| Type | `booking-bundle-groups` |
| Admin collection | `GET/POST /api/v1/booking-bundle-groups` |
| Admin resource | `GET/PATCH/DELETE /api/v1/booking-bundle-groups/{id}` |
| Public catalog | Organization relationships `offered-booking-bundle-groups` and `available-booking-bundles` |

Index is readable without a write permission. Create, update, and delete require `create` / `update` on any resource plus access to the organization (`?o=`).

> [!NOTE]
>
> Shop and native apps must load groups from the organization with `include=offered_booking_bundle_groups` or the related resource `GET /api/v1/organizations/{slug}/offered-booking-bundle-groups`.

### Attributes

| Attribute | Type | Visibility | Description |
| - | - | - | - |
| `name` | string | public | Resolved name for `Accept-Language` |
| `description` | string | public | Resolved description for `Accept-Language` |
| `order_index` | number | public | Display order among groups |
| `created_at` | datetime | public | Creation timestamp |
| `local_name_i18n` | object | admin | Full name translation map |
| `description_i18n` | object | admin | Full description translation map |

Write `name` (required, max 191) and optional `description` / `order_index`. Send translations as `local_name_i18n.{locale}` and `description_i18n.{locale}`.

### Relationships

| Relationship | Type | Include |
| - | - | - |
| `booking_bundles` | has-many `booking-bundles` | `include=booking_bundles` |

Allowed sort: `order_index`, `name`, `created_at`. Allowed filters: `filter[ids]`, `filter[search]`.

***

## Bundle relationship and filters

Every `booking-bundles` resource has a nullable `group` relationship. Include it on list, read, and public catalog requests:

```http theme={null}
GET /api/v1/booking-bundles?include=group&sort=order_index
GET /api/v1/booking-bundles/{slug}?include=group
GET /api/v1/organizations/{slug}/available-booking-bundles?include=group,cover_image,organization.logo_image&sort=order_index
```

### Filters on `booking-bundles` and `available-booking-bundles`

| Filter | Type | Description |
| - | - | - |
| `filter[group_id]` | string | Bundles in one group |
| `filter[groups]` | string or string\[] | Bundles in any of the given group ids |
| `filter[without_group]` | string | Pass `true` (string) to return ungrouped bundles only |

> [!WARNING]
>
> Send `filter[without_group]=true` as a **string**. A JSON boolean query value is rejected by the JSON:API decoder.

```http theme={null}
GET /api/v1/organizations/{slug}/available-booking-bundles?filter[group_id]=16&include=group&sort=order_index
GET /api/v1/organizations/{slug}/available-booking-bundles?filter[without_group]=true&include=group&sort=order_index
```

Assign or clear a group on create/update via the JSON:API relationship (same as services / add-ons):

```json theme={null}
{
  "data": {
    "type": "booking-bundles",
    "attributes": { "name": "10er Karte" },
    "relationships": {
      "group": {
        "data": { "type": "booking-bundle-groups", "id": "16" }
      }
    }
  }
}
```

Set `relationships.group.data` to `null` to ungroup.

***

## Public catalog (shop and native customer apps)

Load the live groups that currently have at least one **live** bundle, then load bundles per group.

**Include on the organization**

```http theme={null}
GET /api/v1/organizations/{slug}?include=offered_booking_bundle_groups,available_booking_bundles.group,available_booking_bundles.cover_image
```

**Related resources**

```http theme={null}
GET /api/v1/organizations/{slug}/offered-booking-bundle-groups?sort=order_index
GET /api/v1/organizations/{slug}/available-booking-bundles?filter[group_id]={groupId}&include=group,cover_image,organization.logo_image&sort=order_index&page[size]=10
GET /api/v1/organizations/{slug}/available-booking-bundles?filter[without_group]=true&include=group,cover_image,organization.logo_image&sort=order_index
```

`offered-booking-bundle-groups` only returns groups that have at least one live bundle. Empty groups do not appear in the shop catalog.

### How to render the package list

Match the web shop: one collapsible section per offered group (title = `name`, optional subtitle = `description`), then an **Other** section for `filter[without_group]=true`.

**1. Load offered groups**

Request `offered_booking_bundle_groups` (or the related resource). Sort by `order_index`.

**2. Load bundles per group**

For each group, request `available-booking-bundles` with `filter[group_id]` set to that group's id, plus `include=group,cover_image` and `sort=order_index`.

**3. Load ungrouped bundles**

Request `available-booking-bundles` with `filter[without_group]=true`. If the collection is empty, hide the Other section.

**4. Reuse service-group UI**

This is the same layout as services (`offered-service-groups` + ungrouped services). Reuse that component; swap the resource type.

> [!TIP]
>
> Do not invent grouping from bundle names. Always use `relationships.group` / `filter[group_id]`. After a group is deleted, previously grouped bundles show up under Other.

***

## Admin (native admin app)

Mirror the web admin:

1. **Bundle form** — taggable group select (create a group by typing a new name, same as service groups). Persist via the `group` relationship. Include `group` when loading a bundle.
2. **Bundle list** — grouped panels by `booking-bundle-groups` (`sort=order_index`), plus an ungrouped section (`filter[without_group]=true`).
3. **CRUD** — create/update/delete groups with `name`, `description`, and translations. On delete, keep the bundles; they become ungrouped.

```http theme={null}
GET /api/v1/booking-bundle-groups?filter[search]=gift&sort=order_index&include=booking_bundles
POST /api/v1/booking-bundle-groups?o={organization_id}
PATCH /api/v1/booking-bundle-groups/{id}?o={organization_id}
DELETE /api/v1/booking-bundle-groups/{id}?o={organization_id}
```

Create payload:

```json theme={null}
{
  "data": {
    "type": "booking-bundle-groups",
    "attributes": {
      "name": "Gift cards",
      "description": "Vouchers and gift packages",
      "order_index": 1
    }
  }
}
```

***

## Example group resource

```json theme={null}
{
  "data": {
    "type": "booking-bundle-groups",
    "id": "16",
    "attributes": {
      "name": "Gift cards",
      "description": "Vouchers and gift packages",
      "order_index": 1,
      "created_at": "2026-08-16T12:00:00+00:00"
    },
    "relationships": {
      "booking_bundles": {
        "data": [
          { "type": "booking-bundles", "id": "geschenkgutschein-50" }
        ]
      }
    }
  }
}
```

***

## Related

* [BookingBundle](https://docs.anny.co/developers/models/booking-bundles.md) — bundle resource; include `group`
* [BookingBundleGroup](https://docs.anny.co/developers/models/booking-bundle-groups.md) — group resource
* [ServiceGroup](https://docs.anny.co/developers/models/service-groups.md) — same grouping pattern for services
* [AddOnGroup](https://docs.anny.co/developers/models/add-on-groups.md) — same grouping pattern for add-ons


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.