Update a Bundle
Change a bundle's details, replace its products, swap or drop its cover, and publish or unpublish it.
Endpoint
PUT /api/v3/bundles/{bundle}
Path Parameters
| Parameter | Type | Description |
|---|---|---|
bundle | string | The bundle's id, or its external_id when id_type=external is set |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
id_type | string | internal | internal (bundle id) or external (external_id) |
include | string | - | Comma-separated: products, prices, availability. See Included blocks |
fields | string | - | Comma-separated list of fields to return, e.g. id,published. Applied after include |
Request Body
Updates are partial: only the fields present in the payload are validated and written. A field you do not send keeps its value, so renaming a bundle never touches its products, cover, state or external_id.
The verb is PUT, but the semantics are a merge, not a replacement: fields absent from the payload are left untouched rather than cleared.
| Field | Type | Notes |
|---|---|---|
name | string | Max 255 characters. Cannot be null |
products | array | Replaces the whole composition, in the order sent. From 2 to 100, each once. See Replacing the products |
products_id_type | string | internal (default) or external, how products names the products. See Products by identifier |
description | string | Max 2,000 characters |
slug | string | Max 255 characters |
external_id | string | Max 64 characters, unique in your store. Send null to clear it. Resending the bundle's own external_id, in any case, is accepted |
published | boolean | true publishes, false unpublishes. See Publishing |
private | boolean | See States |
cover_url | string | Downloads a new cover. null drops the bundle's own cover. Not accepted together with cover. See Cover |
cover | string | Storage path of a new uploaded cover. null drops the bundle's own cover. Not accepted together with cover_url. See Cover |
published_at is not accepted, as on create: the platform sets it.
An external_id already used by another bundle of your store answers 422 with A bundle with this external_id already exists.
Replacing the Products
Send products to replace the composition. The rules are the same as on create: from 2 to 100 products, each once, named by position when one fails. Leave products out to keep the current composition as it is.
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/318" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "products": [1207, 1201] }'
Send products_id_type: external to list them by identifier instead, as described under Products by identifier.
Products the bundle already has are kept as they are, whether you send them by ID or by identifier. A product that stopped being sellable after it was added (it was withdrawn from sale, for example) does not block an update that sends it again: the bundle keeps it, and with ?include=products the response shows its rejection_reason. A product new to the bundle must be sellable, so that same product is refused when you add it to another bundle.
A bundle that keeps an unsellable product stays unavailable. While it is unpublished, it cannot be published until you replace that product.
Publishing
| You send | On a bundle that is | Result |
|---|---|---|
published: true | unpublished | Publishes it, when it can be sold with the products it has after the request |
published: true without products | already published | Keeps it published as it is, without checking whether it can be sold |
published: true with products | already published | Keeps it published, when it can be sold with the new products |
published: false | any | Unpublishes it. Always allowed, also on a bundle that cannot be sold |
no published | any | Leaves the state as it is |
Publishing a bundle that is already published keeps its original published_at; the date does not move. So an integration that sends published: true again without products does not fail when one of the products has stopped being sellable since. When the request also carries products, the bundle is checked again with those products. The update is saved, and the response reports that the bundle cannot be sold in availability when the request asks for ?include=availability.
When the bundle cannot be sold, a published: true that checks it answers 422 under published, naming each reason with its availability code, and nothing in the request is saved, including the other fields it carried:
{
"message": "The bundle cannot be published: some of its products cannot be sold in a bundle (ineligible_products).",
"errors": {
"published": [
"The bundle cannot be published: some of its products cannot be sold in a bundle (ineligible_products)."
]
}
}
A bundle saved as a single-product draft from the dashboard answers the same way:
{
"message": "The bundle cannot be published: it has fewer than 2 products (below_minimum_products).",
"errors": {
"published": [
"The bundle cannot be published: it has fewer than 2 products (below_minimum_products)."
]
}
}
To publish such a draft, send its complete products together with published: true.
Example: Publish
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/318" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "published": true, "private": false }'
Cover
| You send | Result |
|---|---|
cover_url with a URL | Downloads the image and replaces the bundle's cover |
cover with a storage path | Replaces the bundle's cover with that uploaded image |
cover_url: null or cover: null | Drops the bundle's own cover: it goes back to the collage of its products |
| neither | Keeps the current cover |
The image rules are listed under Covers.
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/318" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "cover_url": null }'
Request Examples
Rename, Found by external_id
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/rock-icons?id_type=external" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "name": "Rock Icons: The Complete Set" }'
Change or Clear the external_id
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/318" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "external_id": "rock-icons-2026" }'
Send "external_id": null to clear it.
Unpublish
curl -X PUT "https://yourstore.publica.la/api/v3/bundles/318" \
-H "X-User-Token: your-api-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "published": false }'
Response
200 OK with the updated bundle object under data. It carries products, prices and availability only when the request names them in include, computed after the update.
Errors
| Status | Condition |
|---|---|
401 | Missing or invalid X-User-Token |
403 | Selling is disabled for the store (Selling is disabled for this store.), or the token does not belong to an administrator. A token without access gets 403 even for a bundle that does not exist |
404 | The bundle does not exist in your store, checked before the payload is validated |
422 | Validation failure: name sent as null, fewer than 2 or more than 100 products, a product repeated, not found, ambiguous or not sellable, published: true on a bundle that cannot be sold, published_at sent, an external_id used by another bundle, cover and cover_url together, a cover that cannot be downloaded or is not a supported image, or an include other than products, prices or availability or sent as an array. Nothing is saved |
429 | Rate limit exceeded (20 requests per minute per token, shared with create and with the create and update endpoints of the Add-ons API) |
See Also
- Bundles API Overview: the bundle object, states, covers and retries
- Create a Bundle: the product rules and their errors
- Get a Bundle: read a bundle back