POST
Create a bundle

Authorizations

Authorization
string
header
required

A secret API key. Publishable keys cannot reach this API. A key may carry an expiry, and an expired key is refused exactly like an unknown one, with a 401 that names no reason; check the key's expires_at in the dashboard rather than inferring it from a response. When a merchant rolls a key's secret they choose a grace window of up to 3 days, and for its duration BOTH the new secret and the one it replaced authenticate, so an integration moves over on its own deploy schedule instead of at the instant the button is pressed. Move before the window closes: after it, the old secret is refused. Nothing else about this contract moves with a roll. The key keeps its id and its scopes, so the only thing an integration updates is the credential itself.

Headers

Idempotency-Key
string
required

A unique key per logical write. Replaying a request with the same key returns the first response byte for byte instead of applying the write twice.

Body

application/json

Creates a bundle BARE. The items array in the response is present and EMPTY, which is a true state rather than a placeholder: a bundle with no components is not purchasable, and the storefront reports that as an ordinary out of stock rather than as an error. Add components with POST /api/v1/bundles/{bundleId}/items afterwards. IT DOES NOT CONVERGE PAST THE IDEMPOTENCY WINDOW, and this family has to say so plainly because the retail-location and company families in this same document do converge. product_bundle has NO unique column besides its own id: title is free-form and the patch rewrites it, so nothing stops two identical bundles existing side by side. Replaying the Idempotency-Key returns the recorded 201 without re-running anything, but a retry with a FRESH key mints a SECOND bundle rather than answering 409. Reuse your own key on a retry; there is no server-side dedupe to fall back on. price_minor IS REQUIRED even though the column would take a zero happily, and that is the one that matters. The CHECK permits 0, so an omitted price would mint a bundle that gives its components away for nothing, pass every check on the way in and be refused by nothing downstream: the storefront would render it, the cart would allocate zero across the components, and the first evidence would be a paid order worth nothing. A genuinely free bundle costs one explicit 0. NOTHING IS EMITTED. This family registers no webhook topics, so a client mirroring bundles must poll. The bundle is resolved when a cart is priced rather than snapshotted anywhere, so an active bundle is sellable as soon as it has components. REFUSED BY NAME rather than ignored: items, because components are written one at a time and a whole-array replace here would remove every component the array left out and silently reprice the survivors; component_sum, because it is computed from the component variants' own prices rather than stored and is not published on the read either; price, because the field is price_minor; available_quantity, in_stock and unlimited, because stock is inventory's resource and all three are derived; and the provenance keys (id, store_id, created_at, updated_at), because the server mints and owns them and your api key already names the store. NO KEY MAY BE SENT AS null. Nothing this API writes on a bundle is nullable, so a null answers 400 naming the field; omit the key instead. The whole body is capped at 1 MiB.

title
string
required

Required, and TRUNCATED AT 200 BYTES rather than refused, matching the dashboard. A published API that silently shortens input has to admit it, so read the response back if your titles run long. Blank or whitespace is a 400.

price_minor
integer
required

Required. The bundle's AUTHORITATIVE set price in INTEGER MINOR UNITS of currency_code (1299 is 12.99 in a two-decimal currency), never derived from what the components are worth separately. THIS is the number a shopper is charged, and the cart allocates it across whatever components the bundle has at the time, so it is also what decides what each component contributes per unit.

Required range: 0 <= x <= 1000000000000
currency_code
string
required

Required, and it is what makes price_minor readable. Accepted in any case and STORED LOWERCASE, so a client sending "USD" reads back "usd"; the read contract is already frozen on the stored form. Validated against the ISO 4217 catalogue, so an unknown code is a 400 rather than an unreadable amount.

status
enum<string>

Optional, defaulting to active, which is the column's own default. This is the reversible withdrawal this family offers instead of a delete: disabled takes the bundle out of sale and keeps every component, and one more patch puts it back.

Available options:
active,
disabled

Response

Created

data
object