Skip to content

Update a product

PATCH
/products/{id}
curl --request PATCH \
--url https://example.com/products/019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f \
--header 'Content-Type: application/json' \
--cookie __Host-bottle.session_token=<__Host-bottle.session_token> \
--data '{ "name": "example", "shortCode": "example", "sku": "example", "unitPrice": 1, "priceIncludesVat": true, "vatRate": 1, "charge": "deposit", "chargePeriod": "once", "chargeForProductId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f", "chargeForProductIds": [ "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f" ], "chargeAudience": "both", "gasTypeId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f", "dangerousGoods": { "unNumber": "1978", "shippingName": "PROPANE", "hazardClass": "2.1" }, "weightKg": 1, "volumeLitres": 1, "notes": "example", "kind": "cylinder", "brand": "example", "description": "example", "descriptionHtml": "example", "relatedProductIds": [ "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f" ], "published": true, "shopAudience": "both", "categoryIds": [ "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f" ], "refundRule": "full", "refundPercent": 1, "refundDeductionPence": 1, "refundScale": [ { "upToMonths": 1, "percent": 1, "fixedPence": 1 } ], "refundCountsFrom": "latest", "tradeUnitPrice": 1, "tradePriceIncludesVat": true, "costPrice": 1, "stockLowAt": 1, "stockCounted": true, "tracking": "quantity", "shelfLifeMonths": 1, "homeLocationId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f", "whenOut": "stop", "details": [ { "detailId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f", "value": [ "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f" ] } ], "archived": true }'

Archiving keeps it off new orders and leaves old ones alone, since what was sold last year is a fact about last year. It also takes the product off the business’s shop, and it stays off when un-archived until put back. categoryIds, when sent, replaces the categories it is in.

id
required

UUID v7

string format: uuid

Example

019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f

UUID v7

Media typeapplication/json
object
name
string
>= 1 characters <= 120 characters
shortCode
string | null
<= 20 characters
sku
string | null
<= 64 characters
unitPrice

Pence, as typed. Say whether VAT is in it with priceIncludesVat.

integer
<= 100000000
priceIncludesVat

Whether unitPrice includes VAT. Left out, the business’s own setting (pricesIncludeVat on /tax) applies.

boolean
vatRate

VAT in basis points. Defaults to 2000, the standard 20%.

integer
<= 10000
charge
string | null
Allowed values: deposit hire
chargePeriod
string | null
Allowed values: once daily weekly monthly
chargeForProductId

UUID v7

string | null format: uuid

Example

019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f
chargeForProductIds

Exact cylinder products this charge applies to, replacing the previous selection. May span gas types. Empty uses gasTypeId instead.

Array<string>
<= 500 items
chargeAudience

Who pays a deposit or rent where no price list or agreed amount says otherwise. both: Consumer and Trade customers (the default). consumer: everybody who is not a trade customer, signed in or not. trade: trade customers only. Meaningless on anything that is not a charge.

string
Allowed values: both consumer trade
gasTypeId

UUID v7

string | null format: uuid

Example

019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f
dangerousGoods

Override the gas type’s UN number, proper shipping name and hazard class for this product. Null follows the gas type.

object
unNumber
required

The UN number, four digits without “UN”.

string
/^\d{4}$/

Example

1978
shippingName
required

The proper shipping name from the ADR list, as on the cylinder label.

string
>= 1 characters <= 200 characters

Example

PROPANE
hazardClass
required

The ADR hazard class: 2.1 flammable gas, 2.2 non-flammable non-toxic gas, 2.2 (5.1) non-flammable gas that is also oxidising, 2.3 toxic gas.

string
Allowed values: 2.1 2.2 2.2 (5.1) 2.3

Example

2.1
weightKg
number | null
<= 5000
volumeLitres
integer | null
<= 100000
notes
string | null
<= 2000 characters
kind

What the product is. Null or left out: read from its charge and gas type.

string | null
Allowed values: cylinder deposit hire accessory appliance fuel consumable delivery other
brand
string | null
<= 80 characters
description

Plain text, line breaks kept: a blank line starts a paragraph. Ignored when descriptionHtml is given.

string | null
<= 5000 characters
descriptionHtml

The description with formatting, as HTML, up to 50,000 characters as sent and 20,000 once sanitised (over either is refused with 400, description_too_long). Sanitised on the server: anything outside p, h2, h3, strong, em, ul, ol, li, br and a (http, https or mailto) is removed, keeping its words; scripts, styles, frames, forms, pictures, svg and math go with their contents; every attribute but a link address goes. Wins over description. Null clears it.

string | null
relatedProductIds

Products to show under this one on the shop, in order, replacing any picked before. Up to 8. An empty list goes back to others from the same category.

Array<string>
<= 8 items
published

Put it on the business’s own shop. Off unless asked for. Ignored on an archived product.

boolean
shopAudience

Who may buy it on the shop once published. both: everybody (the default). consumer: anybody not signed in for a trade account, so trade customers do not see it. trade: signed-in trade customers only.

string
Allowed values: both consumer trade
categoryIds

The shop categories it sits in, replacing any it was in.

Array<string>
<= 50 items
refundRule

On a deposit only. full, percent (refundPercent less refundDeductionPence), scale (refundScale), none. Null follows the business’s setting.

string | null
Allowed values: full percent scale none
refundPercent
integer | null
<= 100
refundDeductionPence
integer | null
<= 100000000
refundScale
Array<object> | null
>= 1 items <= 10 items
object
upToMonths
required
integer | null
>= 1 <= 1200
percent
integer
<= 100
fixedPence
integer
<= 10000000
refundCountsFrom

Which hire a returned cylinder is measured from when a customer holds several: latest (the most recent, kinder to the customer) or first (the oldest).

string | null
Allowed values: latest first
tradeUnitPrice

The trade price, pence as typed. Null for none (trade pays the list price).

integer | null
<= 100000000
tradePriceIncludesVat

Whether tradeUnitPrice includes VAT. Left out, the business’s own setting.

boolean
costPrice

What one costs the business, pence without VAT. Null for not known.

integer | null
<= 100000000
stockLowAt

Tell the office at or under this count. Null for never. The count itself is set with POST /products/{id}/stock.

integer | null
<= 1000000
stockCounted

Whether to keep a yard stock figure for this cylinder. Left out, true. False marks it as not counted on the stock list, plans rounds as though there are always enough, and turns off its low-stock and no-figure warnings.

boolean
tracking

Quantity or individual. Left out, quantity. individual is refused unless Assets is open and switched on for the business, and only a cylinder can be tracked.

string
Allowed values: quantity individual
shelfLifeMonths

How many months a fill lasts, 1 to 600, for suggesting the expiry date when stock comes in. Null for no suggestion.

integer | null
>= 1 <= 600
homeLocationId

The yard this cylinder is kept at, when it is not wherever a round loads. Left out or null, every yard keeps it and it goes on with the morning load. Must be one of your yards.

string | null format: uuid

Example

019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f
whenOut

When none are left: stop or continue. Null follows the shop’s own rule.

string | null
Allowed values: stop continue
details

Product details to set, by detail id. Only the details sent change; null clears one. A detail has to be one of the business’s own, not archived, and apply to the kind of product (error: detail_not_for_kind).

Array<object>
<= 50 items
object
detailId
required

UUID v7

string format: uuid

Example

019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f
value
required
Any of:
Array<string>
<= 100 items
archived
boolean

Updated

Media typeapplication/json
object
id
required

UUID v7

string format: uuid
name
required
string
shortCode
required
string | null
sku
required
string | null
unitPrice
required

Pence, exactly as entered. Whether VAT is in this figure is what priceIncludesVat says; the other reading is given beside it for display.

integer
<= 100000000
priceIncludesVat
required

Whether unitPrice includes VAT. False: VAT goes on top when the product is sold. True: unitPrice is the shelf price, and the VAT is worked back out of it on each line.

boolean
unitPriceExVat
required

UnitPrice without VAT, in pence, for display. The same as unitPrice when it excludes VAT or the business is not registered; otherwise derived and rounded. Do not multiply it: amounts are worked out from unitPrice.

integer
unitPriceIncVat
required

UnitPrice with VAT, in pence, for display. The same as unitPrice when it includes VAT or the business is not registered; otherwise derived and rounded.

integer
vatRate
required

VAT in basis points: 2000 is 20%, 500 is 5%, 0 is zero rated.

integer
<= 10000
charge
required
string | null
Allowed values: deposit hire
chargePeriod
required
string | null
Allowed values: once daily weekly monthly
chargeForProductId
required

UUID v7

string | null format: uuid
chargeForProductIds
required
Array<string>
chargeAudience
required

Who pays a deposit or rent where no price list or agreed amount says otherwise. both: Consumer and Trade customers (the default). consumer: everybody who is not a trade customer, signed in or not. trade: trade customers only. Meaningless on anything that is not a charge.

string
Allowed values: both consumer trade
gasTypeId
required

UUID v7

string | null format: uuid
dangerousGoods
required

This product’s own UN number, proper shipping name and hazard class, where they differ from its gas type’s. Null follows the gas type.

object
unNumber
required

The UN number, four digits without “UN”.

string
/^\d{4}$/
shippingName
required

The proper shipping name from the ADR list, as on the cylinder label.

string
>= 1 characters <= 200 characters
hazardClass
required

The ADR hazard class: 2.1 flammable gas, 2.2 non-flammable non-toxic gas, 2.2 (5.1) non-flammable gas that is also oxidising, 2.3 toxic gas.

string
Allowed values: 2.1 2.2 2.2 (5.1) 2.3
weightKg
required
number | null
volumeLitres
required
integer | null
notes
required
string | null
kind
required

What the product is. The one set on it, or when none has been, read from it: a charge makes it a deposit or hire, a gas type a cylinder, anything else other.

string
Allowed values: cylinder deposit hire accessory appliance fuel consumable delivery other
kindSet
required

Whether kind was set rather than read from the product.

boolean
brand
required
string | null
description
required

The description as plain text, with a blank line between paragraphs and a line per list item. Made from descriptionHtml, for search, previews and anywhere HTML cannot go.

string | null
descriptionHtml
required

The description with its formatting, as sanitised HTML: only p, h2, h3, strong, em, ul, ol, li, br and a. A link has only an http, https or mailto href, rel=“noopener noreferrer nofollow”, and target=“_blank” for a web address. Sanitised again on every read; still treat it as HTML from a third party.

string | null
relatedProductIds
required

Products picked to show under this one on the shop, in order, up to 8. Empty: the shop shows others from the same category.

Array<string>
published
required

On the business’s own shop. Shown there only once the shop is switched on. Never true on an archived product.

boolean
shopAudience
required

Who may buy it on the shop once published. both: everybody (the default). consumer: anybody not signed in for a trade account, so trade customers do not see it. trade: signed-in trade customers only.

string
Allowed values: both consumer trade
refundRule
required

On a deposit: what comes back when a cylinder is returned. Null follows the business’s own setting (invoice settings).

string | null
Allowed values: full percent scale none
refundPercent
required
integer | null
<= 100
refundDeductionPence
required
integer | null
refundScale
required
Array<object> | null
>= 1 items <= 10 items
object
upToMonths
required
integer | null
>= 1 <= 1200
percent
integer
<= 100
fixedPence
integer
<= 10000000
refundCountsFrom
required

Which hire a returned cylinder is measured from. Null is latest.

string | null
Allowed values: latest first
tradeUnitPrice
required

What a trade customer pays, on the shop and on office orders, when no rate has been agreed with them, pence as typed. Null: the list price. A customer’s agreed rate still comes first, and price rules for trade work off this.

integer | null
<= 100000000
tradePriceIncludesVat
required
boolean
tradePriceExVat
required
integer | null
tradePriceIncVat
required
integer | null
costPrice
required

What one costs the business, pence without VAT. Optional; read by cost-plus price rules. Never shown to customers.

integer | null
<= 100000000
stockOnHand
required

How many are on the shelf, for things sold on the shop that are not cylinders. Null is not counted: always available. Set and moved with POST /products/{id}/stock, never here. May be below nought when the product keeps selling once none are left.

integer | null
stockLowAt
required

At or under this count the office is told, and the shop may say low stock. Null is never.

integer | null
stockCounted
required

Whether the yard’s cylinder stock keeps a figure for this product. False: the stock list marks it as not counted, rounds plan as though there are always enough, and nothing warns that it is low or has no figure. Only read on a cylinder.

boolean
tracking
required

How this product’s stock is recorded. quantity: as a count. individual: each cylinder by its own serial number, once Assets is open and switched on for the business. Only read on a cylinder.

string
Allowed values: quantity individual
shelfLifeMonths
required

How many months a fill lasts, for a tracked cylinder. Used to suggest the expiry date when stock comes in. Null: no suggestion.

integer | null
homeLocationId
required

The yard this cylinder is kept at, when it is not wherever a round loads. A round loading at another yard gets a stop at this one to collect what its orders need, before the doors that need it. Null for a cylinder every yard keeps. Only read on a cylinder.

string | null format: uuid
whenOut
required

What the shop does when none are left, for this product. Null follows the shop’s own rule (Settings, Shop). stop: off sale. continue: keeps selling, shown as ships when back in stock.

string | null
Allowed values: stop continue
images
required
Array<object>
object
id
required

UUID v7

string format: uuid
alt
required

What the picture shows, for screen readers and search.

string | null
sortOrder
required
integer
width
required

Of the original, in pixels.

integer
height
required
integer
thumb
required

Fits inside 200 pixels. WebP.

string
card
required

Fits inside 600 pixels. WebP.

string
large
required

Fits inside 1600 pixels. WebP.

string
categoryIds
required
Array<string>
links
required
Array<object>
object
source
required
string
Allowed values: woocommerce shopify
externalId
required
string
externalVariantId
required
string | null
externalSku
required
string | null
lastSyncedAt
required
string | null format: date-time
details
required

The product details it has, in the order the details are listed (GET /product-details). Archived details are left out; their values are kept.

Array<object>
object
detailId
required

UUID v7

string format: uuid
value
required
Any of:
Array<string>
<= 100 items
archivedAt
required
string | null format: date-time
createdAt
required
string format: date-time
updatedAt
required
string format: date-time

Example

{
"id": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f",
"name": "19kg propane",
"shortCode": "19",
"sku": "PROP-19",
"unitPrice": 5400,
"unitPriceExVat": 5400,
"unitPriceIncVat": 6480,
"vatRate": 2000,
"charge": "deposit",
"chargePeriod": "once",
"chargeForProductId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f",
"chargeForProductIds": [
"019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f"
],
"chargeAudience": "both",
"gasTypeId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f",
"dangerousGoods": {
"unNumber": "1978",
"shippingName": "PROPANE",
"hazardClass": "2.1"
},
"weightKg": 38.5,
"volumeLitres": 60,
"kind": "cylinder",
"brand": "Flogas",
"descriptionHtml": "<p>A <strong>13kg</strong> propane refill.</p><ul><li>Same day delivery</li></ul>",
"relatedProductIds": [
"019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f"
],
"shopAudience": "both",
"refundRule": "full",
"refundCountsFrom": "latest",
"stockOnHand": 12,
"stockLowAt": 3,
"tracking": "quantity",
"homeLocationId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f",
"whenOut": "stop",
"images": [
{
"id": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f"
}
],
"categoryIds": [
"019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f"
],
"links": [
{
"source": "woocommerce",
"externalId": "1043"
}
],
"details": [
{
"detailId": "019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f",
"value": [
"019205d1-6e7a-7c3b-9f6e-3a2b1c0d9e8f"
]
}
]
}

The description is over 50,000 characters as sent or 20,000 once sanitised (error: description_too_long). Nothing was changed.

Media typeapplication/json
object
error
required
string
message
required
string

Example

{
"error": "not_found",
"message": "Customer not found"
}

Not signed in

Media typeapplication/json
object
error
required
string
message
required
string

Example

{
"error": "not_found",
"message": "Customer not found"
}

Not a member, or without the permission this needs

Media typeapplication/json
object
error
required
string
message
required
string

Example

{
"error": "not_found",
"message": "Customer not found"
}

Not found

Media typeapplication/json
object
error
required
string
message
required
string

Example

{
"error": "not_found",
"message": "Customer not found"
}

Validation failed

Media typeapplication/json
object
error
required
string
Allowed values: validation
message
required
string
issues
required
Array<object>
object
path
required
string
message
required
string

Example

{
"error": "validation",
"issues": [
{
"path": "email",
"message": "Invalid email"
}
]
}