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

# REST API

> List, create, update and delete bundles, and read orders and analytics

The REST API gives you the same control over your bundles as the dashboard. [Create an API key](/settings/api-and-mcp) first, then send it with every request:

```bash theme={null}
curl "<your-app-url>/api/v1/bundles" \
  -H "Authorization: Bearer <your-api-key>"
```

`<your-app-url>` is shown on **Settings** → **API & MCP**. All requests and responses are JSON. Errors look like this:

```json theme={null}
{ "error": { "message": "Bundle not found" } }
```

## Endpoints

| Method and path | Access | Description |
| - | - | - |
| `GET /api/v1/bundles` | Read | List bundles. Filter with `status` (`ACTIVE` or `PAUSED`), page with `page` and `limit` (max 100). |
| `GET /api/v1/bundles/{id}` | Read | Get one bundle with its products and offers. |
| `POST /api/v1/bundles` | Write | Create a bundle. |
| `PATCH /api/v1/bundles/{id}` | Write | Update the fields you send. `products` and `offers` replace the whole list when included. |
| `DELETE /api/v1/bundles/{id}` | Write | Delete a bundle. Safe to repeat. |
| `GET /api/v1/orders` | Read | List orders that contained a bundle. Filter with `bundleId`. Customer details aren't included. |
| `GET /api/v1/analytics` | Read | Use `range=7d`, `30d` or `90d`, or `range=custom&start=YYYY-MM-DD&end=YYYY-MM-DD`. |

## List bundles

```bash theme={null}
curl "<your-app-url>/api/v1/bundles?status=ACTIVE&limit=2" \
  -H "Authorization: Bearer <your-api-key>"
```

```json theme={null}
{
  "bundles": [
    {
      "id": 42,
      "name": "Skincare duo",
      "title": "Buy both and save",
      "bundleType": "FIXED",
      "status": "ACTIVE",
      "products": [
        { "productId": "gid://shopify/Product/1234567890", "title": "Cleanser", "handle": "cleanser", "quantity": 1, "isPrimary": true, "defaultVariantId": null },
        { "productId": "gid://shopify/Product/2345678901", "title": "Moisturizer", "handle": "moisturizer", "quantity": 1, "isPrimary": false, "defaultVariantId": null }
      ],
      "offers": [],
      "startsAt": null,
      "endsAt": null,
      "createdAt": "2026-10-01T09:30:00.000Z",
      "updatedAt": "2026-10-08T14:12:00.000Z"
    }
  ],
  "page": 1,
  "limit": 2,
  "total": 1
}
```

Each bundle also includes its display and targeting settings (`buttonText`, `badgeText`, `minProducts`, `maxProducts`, `targetType`, `combinesWith` and more). Bundles created from the dashboard may include more settings than the API accepts.

## Create a bundle

You supply only the Shopify product IDs. The product title, handle, image and variants are loaded from your store.

```bash theme={null}
curl -X POST "<your-app-url>/api/v1/bundles" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Skincare duo",
    "title": "Buy both and save",
    "bundleType": "FIXED",
    "status": "PAUSED",
    "products": [
      { "productId": "gid://shopify/Product/1234567890", "quantity": 1, "isPrimary": true },
      { "productId": "gid://shopify/Product/2345678901", "quantity": 1 }
    ],
    "offers": [
      { "title": "Both", "subTitle": "", "tag": "", "label": "", "selected": true, "order": 0, "discountType": "PERCENTAGE", "quantity": 1, "value": 10 }
    ]
  }'
```

The response is `201` with the created bundle:

```json theme={null}
{
  "bundle": {
    "id": 43,
    "name": "Skincare duo",
    "title": "Buy both and save",
    "bundleType": "FIXED",
    "status": "PAUSED",
    "products": [
      { "productId": "gid://shopify/Product/1234567890", "title": "Cleanser", "handle": "cleanser", "quantity": 1, "isPrimary": true, "defaultVariantId": null },
      { "productId": "gid://shopify/Product/2345678901", "title": "Moisturizer", "handle": "moisturizer", "quantity": 1, "isPrimary": false, "defaultVariantId": null }
    ],
    "offers": [
      { "title": "Both", "subTitle": "", "tag": "", "label": "", "selected": true, "order": 0, "discountType": "PERCENTAGE", "quantity": 1, "value": 10, "imageUrl": null }
    ],
    "createdAt": "2026-10-09T10:00:00.000Z",
    "updatedAt": "2026-10-09T10:00:00.000Z"
  }
}
```

Bundle rules match the dashboard:

* **Fixed** bundles need at least two products.
* **BOGO** bundles need at least one primary product (`isPrimary: true`) and one other product.
* **Volume** bundles need a title on every offer, and either products or `targetType` of `ALL_PRODUCTS` or `COLLECTIONS` with `targetCollectionIds`.
* **Mix & Match** bundles need at least one product or `collectionIds`.
* `bundleType` can't be changed after creation. Frequently bought together (FBT) bundles can't be created or changed through the API yet.

`discountType` is one of `PERCENTAGE`, `FLAT`, `FIXED_PER_ITEM`, `FIXED_PRICE` or `FREE_GIFT`.

## Update a bundle

Send only what should change. This publishes a bundle:

```bash theme={null}
curl -X PATCH "<your-app-url>/api/v1/bundles/43" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "ACTIVE" }'
```

The response is `200` with the updated bundle in the same shape as above.

## Delete a bundle

```bash theme={null}
curl -X DELETE "<your-app-url>/api/v1/bundles/43" \
  -H "Authorization: Bearer <your-api-key>"
```

```json theme={null}
{ "id": 43, "deleted": true }
```

If the request returns `502`, the bundle was deleted but Shopify wasn't updated. Send the same request again to finish the sync.

## List orders

```bash theme={null}
curl "<your-app-url>/api/v1/orders?bundleId=42&limit=2" \
  -H "Authorization: Bearer <your-api-key>"
```

```json theme={null}
{
  "orders": [
    {
      "orderId": "gid://shopify/Order/9001",
      "orderNumber": "#1001",
      "bundleId": 42,
      "bundleName": "Skincare duo",
      "orderTotal": "126.00",
      "bundleTotal": "126.00",
      "bundleDiscount": "14.00",
      "purchasedAt": "2026-10-07T18:20:00.000Z",
      "createdAt": "2026-10-07T18:20:05.000Z"
    }
  ],
  "page": 1,
  "limit": 2,
  "total": 1
}
```

## Analytics

```bash theme={null}
curl "<your-app-url>/api/v1/analytics?range=7d" \
  -H "Authorization: Bearer <your-api-key>"
```

The response has a `range` (`start` and `end` as ISO timestamps), a `summary` for the whole shop, a `bundles` array with views, add-to-carts, orders, revenue, discount, conversion rate and ROI per bundle, and a daily `series` of revenue and orders.

## Error codes

| Status | Meaning |
| - | - |
| `400` | The request is invalid. The message says what to fix, for example a missing field or a product that doesn't exist in your store. |
| `401` | The key is missing, wrong or revoked. |
| `403` | The key is read only. Create a key with write access. |
| `404` | The bundle doesn't exist in your shop. |
| `405` | The method isn't supported on this path. |
| `409` | The bundle changed while saving (retry), or the app isn't installed or needs to be reopened in the Shopify admin. |
| `422` | Publishing would exceed Shopify's 10KB bundle data limit. Target collections, use fewer products, or create the bundle as `PAUSED`. |
| `502` | The change was saved but syncing with Shopify failed. Repeat the request, or open the app and save the bundle again. |

<Tip>Need a field or endpoint that isn't here? Contact us at [support@getappfox.com](mailto:support@getappfox.com).</Tip>


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