Documentation
Bundles Admin API
The Bundles Admin API lets you manage bundles programmatically from your own scripts and systems: sync bundles from a PIM, build seasonal bundles in bulk, or wire bundle creation into your existing catalog tooling. It covers the same operations as the app's bundle editor, so anything you create here behaves exactly like a bundle built in the app.
This API manages bundle configuration. To add bundles to the cart on your storefront, see Cart API Integration.
Base URL
https://api.flexbundles.com/v1
Authentication
Every request needs an API key sent as a bearer token:
Authorization: Bearer fxb_your_api_key
Generate your key from the Settings page in the Flex Bundles app. The key is shown once at generation time and only a hash is stored, so copy it somewhere safe. Regenerating or revoking a key stops the old key working immediately.
Keep the key server-side. It grants full bundle management for your store, so never ship it in theme code or client-side JavaScript.
Making Requests
The examples in this guide show cURL, Node.js, and Ruby. The Node.js and Ruby examples use these small helpers, which need nothing beyond the standard runtime (Node.js 18+, any recent Ruby):
const API = "https://api.flexbundles.com/v1";
const KEY = process.env.FLEX_BUNDLES_API_KEY;
async function flexBundles(path, method = "GET", body) {
const res = await fetch(`${API}${path}`, {
method,
headers: {
"Authorization": `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${json.error}`);
return json;
}
require "net/http"
require "json"
API = "https://api.flexbundles.com/v1"
KEY = ENV.fetch("FLEX_BUNDLES_API_KEY")
def flex_bundles(path, method: :get, body: nil)
uri = URI("#{API}#{path}")
request = Net::HTTP.const_get(method.capitalize).new(uri)
request["Authorization"] = "Bearer #{KEY}"
request["Content-Type"] = "application/json"
request.body = body.to_json if body
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
json = JSON.parse(response.body)
raise "#{response.code}: #{json["error"]}" unless response.is_a?(Net::HTTPSuccess)
json
end
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/flex-bundles |
List flex bundles |
| POST | /v1/flex-bundles |
Create a flex bundle |
| GET | /v1/flex-bundles/{id} |
Get one flex bundle |
| PUT or PATCH | /v1/flex-bundles/{id} |
Update a flex bundle |
| DELETE | /v1/flex-bundles/{id} |
Delete a flex bundle |
| GET | /v1/fixed-bundles |
List fixed bundles |
| POST | /v1/fixed-bundles |
Create a fixed bundle |
| GET | /v1/fixed-bundles/{id} |
Get one fixed bundle |
| PUT or PATCH | /v1/fixed-bundles/{id} |
Update a fixed bundle |
| DELETE | /v1/fixed-bundles/{id} |
Delete a fixed bundle |
All request and response bodies are JSON. IDs (bundle, product, variant) are numeric Shopify IDs without the gid:// prefix.
Flex Bundles
Flex bundles attach to a product and let customers build their own bundle on your storefront. See Bundle Types for how they work.
Create a flex bundle
curl -X POST https://api.flexbundles.com/v1/flex-bundles \
-H "Authorization: Bearer fxb_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_id": "10309675680045",
"security": { "minimum_price": 25 },
"settings": { "title": "Build Your Own Kit" }
}'
const { bundle } = await flexBundles("/flex-bundles", "POST", {
product_id: "10309675680045",
security: { minimum_price: 25 },
settings: { title: "Build Your Own Kit" },
});
result = flex_bundles("/flex-bundles", method: :post, body: {
product_id: "10309675680045",
security: { minimum_price: 25 },
settings: { title: "Build Your Own Kit" },
})
bundle = result["bundle"]
| Field | Type | Required | Description |
|---|---|---|---|
product_id |
string or number | Yes | The parent product. One flex bundle per product. |
active |
boolean | No | Defaults to true. |
security.minimum_price |
number | No | Reject carts where the bundle price is below this. Defaults to 0 (off). |
security.minimum_component_price |
number | No | Reject carts where any component price is below this. Defaults to 0 (off). |
settings |
object | No | Optional presentation settings (title, image, walkthrough_url). |
Response is 201 Created with the full bundle. The id is generated by the API:
{
"bundle": {
"id": 1783440131288415,
"active": true,
"bundle_type": "flex",
"parent": { "product_id": 10309675680045 },
"security": { "minimum_price": 25, "minimum_component_price": 0 },
"settings": { "title": "Build Your Own Kit" }
}
}
Update a flex bundle
PUT and PATCH behave identically: send only the fields you want to change.
curl -X PATCH https://api.flexbundles.com/v1/flex-bundles/1783440131288415 \
-H "Authorization: Bearer fxb_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "active": false }'
await flexBundles("/flex-bundles/1783440131288415", "PATCH", { active: false });
flex_bundles("/flex-bundles/1783440131288415", method: :patch, body: { active: false })
product_id cannot be changed on a flex bundle. Delete the bundle and create a new one on the other product instead.
Fixed Bundles
Fixed bundles attach to a specific variant with a pre-set component list. Adding the parent variant to the cart automatically includes the components.
Create a fixed bundle
curl -X POST https://api.flexbundles.com/v1/fixed-bundles \
-H "Authorization: Bearer fxb_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_id": "10313045967149",
"variant_id": "52212525334829",
"components": [
{ "product_id": "10309675221293", "variant_id": "52199500808493", "quantity": 2 },
{ "product_id": "10309675286829", "variant_id": "52199500972333", "quantity": 1 }
]
}'
const { bundle } = await flexBundles("/fixed-bundles", "POST", {
product_id: "10313045967149",
variant_id: "52212525334829",
components: [
{ product_id: "10309675221293", variant_id: "52199500808493", quantity: 2 },
{ product_id: "10309675286829", variant_id: "52199500972333", quantity: 1 },
],
});
result = flex_bundles("/fixed-bundles", method: :post, body: {
product_id: "10313045967149",
variant_id: "52212525334829",
components: [
{ product_id: "10309675221293", variant_id: "52199500808493", quantity: 2 },
{ product_id: "10309675286829", variant_id: "52199500972333", quantity: 1 },
],
})
bundle = result["bundle"]
| Field | Type | Required | Description |
|---|---|---|---|
product_id |
string or number | Yes | The parent product. |
variant_id |
string or number | Yes | The parent variant. Must belong to product_id. One fixed bundle per variant. |
components |
array | Yes | At least one component (see below). |
active |
boolean | No | Defaults to true. |
settings |
object | No | Optional presentation settings (title, image, walkthrough_url). |
Each component:
| Field | Type | Required | Description |
|---|---|---|---|
variant_id |
string or number | Yes | The component variant. Use 0 to match any variant of product_id. |
product_id |
string or number | No | The component's product. Recommended. |
quantity |
integer | No | Defaults to 1. Must be at least 1. |
attributes |
object | No | Extra line item properties stamped on this component in the cart. |
Response is 201 Created with the full bundle.
Note: saving a fixed bundle also syncs inventory behavior on the parent variant. If every component is in stock the parent variant's inventory tracking is turned off (it is a virtual item holding no stock of its own); if any component is out of stock the parent is set out of stock so the bundle cannot be oversold.
Update a fixed bundle
Send only what changes. components, when provided, replaces the whole array.
curl -X PATCH https://api.flexbundles.com/v1/fixed-bundles/1783369970901365 \
-H "Authorization: Bearer fxb_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"components": [
{ "product_id": "10309675221293", "variant_id": "52199500808493", "quantity": 3 }
]
}'
await flexBundles("/fixed-bundles/1783369970901365", "PATCH", {
components: [
{ product_id: "10309675221293", variant_id: "52199500808493", quantity: 3 },
],
});
flex_bundles("/fixed-bundles/1783369970901365", method: :patch, body: {
components: [
{ product_id: "10309675221293", variant_id: "52199500808493", quantity: 3 },
],
})
A fixed bundle's parent cannot be changed: product_id and variant_id are fixed at creation. To move a bundle to a different variant, delete it and create a new one.
Listing and Reading
curl https://api.flexbundles.com/v1/flex-bundles \
-H "Authorization: Bearer fxb_your_api_key"
const { bundles } = await flexBundles("/flex-bundles");
bundles = flex_bundles("/flex-bundles")["bundles"]
GET /v1/flex-bundles and GET /v1/fixed-bundles return lightweight summaries:
{
"bundles": [
{
"id": 1783369970901365,
"product_id": 10309675286829,
"variant_id": 52199500972333,
"active": true,
"created_at": "2026-07-02T18:20:11.000Z",
"updated_at": "2026-07-08T09:14:32.000Z"
}
]
}
variant_id appears on fixed bundles only. Fetch GET /v1/{type}-bundles/{id} for the full configuration, which returns the same shape as create and update responses plus created_at and updated_at.
Deleting
Delete a flex bundle:
curl -X DELETE https://api.flexbundles.com/v1/flex-bundles/1783440131288415 \
-H "Authorization: Bearer fxb_your_api_key"
await flexBundles("/flex-bundles/1783440131288415", "DELETE");
flex_bundles("/flex-bundles/1783440131288415", method: :delete)
Delete a fixed bundle:
curl -X DELETE https://api.flexbundles.com/v1/fixed-bundles/1783369970901365 \
-H "Authorization: Bearer fxb_your_api_key"
await flexBundles("/fixed-bundles/1783369970901365", "DELETE");
flex_bundles("/fixed-bundles/1783369970901365", method: :delete)
Returns { "deleted": true, "id": 1783369970901365 }. Deleting removes the bundle configuration and, for fixed bundles, restores the parent variant's inventory tracking and cleans up component references. Orders already placed are unaffected.
Errors
Errors return a JSON body with a single error message:
{ "error": "This product already has a flex bundle. Edit the existing bundle instead of creating a new one." }
| Status | Meaning |
|---|---|
| 400 | Invalid input: malformed JSON, non-numeric ID, bad field type or value. |
| 401 | Missing or invalid API key, or the app's access to your store needs re-authorizing (open the app in Shopify admin, then retry). |
| 402 | An active Flex Bundles subscription is required. |
| 404 | No bundle with that ID (or it is a different bundle type). |
| 405 | HTTP method not supported on that path. |
| 409 | The product or variant already has a bundle of that type. |
| 422 | The product or variant does not exist, the variant does not belong to the product, or you tried to change a bundle's parent (product_id or variant_id). |
Good to Know
- Bundles created through the API appear in the Flex Bundles app immediately, and vice versa. There is one source of truth.
- Send write requests sequentially rather than in parallel. Concurrent writes from multiple sources can race on the bundle registry.
- There is one API key per store. Rotate it any time from the app; the old key stops working the moment a new one is generated.