Bulk create or update products + variants
Each item is either a create or an update:
- Create: omit
productId.name,category, andvariant.nameare required. - Update product: pass
productId. Only the supplied fields are patched. - Update variant: pass both
productIdandvariantId. Variant fields (undervariant.*) are patched; supplying any withoutvariantIderrors withvariantId_required_for_variant_patch.
Returns 201 if any row was created, 200 if every row was an update or errored. Per-row failures are listed in errors; successful rows in results.
A row that fails does not roll back the rows around it, so a batch of 500 with two bad references still writes
498. Match errors[].index back to the row you sent. Send the batch again with Idempotency-Key to retry
safely: a replay returns the first response rather than writing twice.
Errors (per row, in errors)
missing_required_fields_on_create— a create row withoutname,categoryorvariant.name.product_not_found—productIdis unknown or belongs to another tenant.variant_not_found—variantIdisn’t on the product named byproductId.supplier_not_found/supplier_archived— asupplierIdsentry that can’t be used.supplier_default_not_allowed— the hidden Default supplier can’t be assigned explicitly.supplierMinPacks_supplier_mismatch— a minimum names a supplier that isn’t on the variant after the patch.hireable_requires_material_product—hireable: trueon a flower.
Whole-request failures: 422 too_many_items above 500 rows, and 422 wrong_id_type:… or
422 invalid_id_format when a prefixed ID in the batch doesn’t decode.
POST
/api/v1/productsAuthorization
AuthorizationBearer token (bloomcount_...) · headerrequiredHeader parameters
Idempotency-KeystringReplays return the cached response. 24h TTL, scoped per API key.
Request body
requiredapplication/jsonitemsProductItem[]requiredmax items 500
Show propertiesHide properties
Array of
ProductItemproductIdstringvariantIdstringnamestringcategorystringproductTypestringrequired- `flower` — fresh stems. Priced per-stem or per-pack via `pricingMethod`. SKUs are not used; identify variants by ID.
- `material` — sundries (foam, ribbon, vases). Carry a SKU and are typically priced per unit.
default: "flower"
Allowed:
flowermaterialseasonalityobject | nullShow propertiesHide properties
startMonthintegermin 1 · max 12
endMonthintegermin 1 · max 12
variantVariantVariant fields. Some apply only when the parent product is `flower`, others only when `material`. Common fields apply to both. Use the Flower / Material / All filter above to focus on the relevant set.
Show propertiesHide properties
namestringBoth types. Display label for the variant (e.g. `50cm`, `Standard`, `Large`).
stemLengthinteger | nullFlower only. Length of a single stem, in cm.
unitsPerPackinteger | nullBoth types. How many physical units a supplier pack contains. For flowers this is stems-per-bunch; for materials it's units-per-pack. Used to convert between per-stem and per-pack pricing.
colorsstring[]Flower only. Colour tags used for filtering and palette matching.
gradestring | nullFlower only. Supplier grade (e.g. `A1`, `Premium`).
brandstring | nullMaterial only. Manufacturer / supplier brand.
materialstring | nullMaterial only. What the item is made of (e.g. `glass`, `ceramic`, `oasis foam`).
dimensionLengthnumber | nullMaterial only. Physical length, in cm.
dimensionWidthnumber | nullMaterial only. Physical width, in cm.
dimensionHeightnumber | nullMaterial only. Physical height, in cm.
urlstring | nullBoth types. External product URL (supplier page, catalogue link).
skustring | nullMaterial only. Stock-keeping unit. Flowers don't carry SKUs — identify flower variants by ID instead.
supplierIdsstring[]Both types. Suppliers that sell this specific variant, referenced by `supplierId` (see `GET /api/v1/suppliers`). When omitted on create, the variant is attached to the hidden Default supplier so it satisfies the ≥1-supplier invariant. When omitted on update, the variant's existing supplier list is preserved.
Unknown / cross-tenant / archived IDs return the row error `supplier_not_found` (or `supplier_archived`). The hidden Default supplier id can't be passed and returns `supplier_default_not_allowed`.
supplierMinPacksobject[]Both types. Per-supplier minimum order quantity (MOQ), in packs. When the order sheet is built for an event, packs for a given (variant, supplier) line are rounded up to the supplier's `minPacks` — used when a supplier insists on (e.g.) "minimum 5 packs per order" regardless of how few stems you actually need.
Each entry references a supplier on this variant's `supplierIds`. An entry pointing at a supplier that isn't on the list (after the patch is applied) returns `supplierMinPacks_supplier_mismatch`. Entries with `minPacks <= 1` are dropped server-side (they're a no-op constraint).
Show propertiesHide properties
Array of
objectsupplierIdstringrequiredminPacksintegerrequiredmin 2
hireablebooleanMaterial only. When `true`, the variant is studio-owned and rentable by the day. Daily fees aren't set here — they live on a `productType: "hire"` price list (see the Price Lists endpoints).
Setting `hireable: true` on a non-material product (or patching a non-material variant to be hireable) returns the row error `hireable_requires_material_product`.
inventoryQtyinteger | nullMaterial only. Number of units owned. Read responses return `null` when `hireable: false`. Only persisted on write when `hireable: true`.
min 0
Responses
200Update-only batch
resultsobject[]requiredRows that were written.
Show propertiesHide properties
Array of
objectindexintegerrequiredPosition of this row in the items array you sent, zero-based.
actionstringrequiredWhether the row created a product or patched an existing one.
Allowed:
createdupdatedproductIdstringrequiredThe product written. New on a create, echoed back on an update.
variantIdstringThe variant written, when the row touched one.
errorsobject[]requiredRows that failed. Every other row still applied.
Show propertiesHide properties
Array of
objectindexintegerrequiredPosition of the failing row in the array you sent, zero-based.
errorstringrequired201At least one row created
resultsobject[]requiredRows that were written.
Show propertiesHide properties
Array of
objectindexintegerrequiredPosition of this row in the items array you sent, zero-based.
actionstringrequiredWhether the row created a product or patched an existing one.
Allowed:
createdupdatedproductIdstringrequiredThe product written. New on a create, echoed back on an update.
variantIdstringThe variant written, when the row touched one.
errorsobject[]requiredRows that failed. Every other row still applied.
Show propertiesHide properties
Array of
objectindexintegerrequiredPosition of the failing row in the array you sent, zero-based.
errorstringrequired401Missing or invalid API key, or revoked / expired.
errorstringrequired403Authenticated, but the key lacks the required scope or the tenant has no API access.
errorstringrequired422Request body or arguments failed validation.
errorstringrequired429Per-minute rate limit (60/min) or per-month cap exceeded.
errorstringrequired