Designs
A design is artwork placed on a product (a tee, hoodie, bottle, …) and sold in your shops. It is what an order line points at. Designs are created and edited in the web app's design studio (or over MCP); the API reads them so your systems can find what to order, at what price, and in which variants.
Everything below is read-only. GET /v1/designs and GET /v1/designs/{id} need
the designs:read scope.
Listing designs
GET /v1/designs?status=active&limit=50
Authorization: Bearer mchp_…
Cursor-paginated (see Pagination, errors & rate limits). Filter with
status (draft, active or inactive); only active designs can be ordered.
Designs you have deleted never appear.
Each row is a summary:
| Field | Meaning |
|---|---|
id |
Public design id (an 8-character nano id, e.g. d8Xk2mQz). Use it as design_id on order lines |
name, status |
As shown in the web app |
product |
The underlying product: id, title, thumbnail_url |
colours, sizes |
The option names the design is sold in — you can identify an order line by these instead of variant_id |
retail_price, currency |
The tax-inclusive price the design sells for in your shops; null when unpriced |
purchase_price, purchase_tax |
What your account pays Merchable for one unit, ex tax, plus the tax on it — see Two prices |
thumbnail_url |
Front mockup |
delivery_type |
delivery, pickup or delivery_and_pickup — what the design's shop settings allow |
created_at, updated_at |
ISO 8601 |
GET /v1/shops/{id}/designs lists the designs on sale in one shop with the same
rows.
Reading one design
GET /v1/designs/{id} returns the summary plus everything an order line needs:
{
"data": {
"id": "d8Xk2mQz",
"name": "Club Tee",
"status": "active",
"retail_price": { "amount": "39.00", "currency": "AUD" },
"currency": "AUD",
"purchase_price": { "amount": "23.50", "currency": "AUD" },
"purchase_tax": { "amount": "2.35", "currency": "AUD" },
"purchase_breakdown": {
"product_base": { "amount": "15.00", "currency": "AUD" },
"extra_placement_fees": { "amount": "8.50", "currency": "AUD" },
"print_method_premium": { "amount": "0.00", "currency": "AUD" }
},
"quantity_tiers": [
{ "min_quantity": 1, "max_quantity": 9, "price": { "amount": "15.00", "currency": "AUD" } },
{ "min_quantity": 10, "max_quantity": null, "price": { "amount": "12.00", "currency": "AUD" } }
],
"description": "Season 2026 club tee",
"stock_control_enabled": true,
"preorder_enabled": false,
"collections": [{ "id": "9b2f…", "name": "Club kit" }],
"personalisation_zones": [
{ "id": "4d1c8f2a-…", "label": "Player name", "placement": "back", "required": true,
"min_characters": 1, "max_characters": 12, "placeholder": "SMITH" }
],
"variants": [
{ "id": "7c9e…", "colour": "Black", "size": "L", "sku": null, "active": true,
"purchase_price": { "amount": "23.50", "currency": "AUD" },
"purchase_tax": { "amount": "2.35", "currency": "AUD" },
"status": "available", "quantity": 12 },
{ "id": "1f0a…", "colour": "Black", "size": "XL", "sku": null, "active": true,
"purchase_price": { "amount": "23.50", "currency": "AUD" },
"purchase_tax": { "amount": "2.35", "currency": "AUD" },
"status": "out_of_stock", "quantity": 0 }
],
"previews": { "front": "https://…/front.png", "back": "https://…/back.png" }
}
}
| Field | Meaning |
|---|---|
purchase_breakdown |
How purchase_price is made up — see Two prices |
quantity_tiers |
The product's quantity breaks for your country — see Quantity tiers |
description |
Free text from the web app |
stock_control_enabled |
Whether variants track shelf stock (otherwise they are made to order) |
preorder_enabled |
Whether empty shelves are sold as preorders |
collections |
The collections the design belongs to |
personalisation_zones |
Customer-text zones — see Personalisation zones |
variants |
Every colour × size the design exists in — see Variants |
previews |
Placement name → current mockup image URL |
A design from another account, or one you have deleted, is a 404.
Variants
A variant is one colour × size of the design. An order line names it either by
variant_id (the id here) or by colour and/or size; the names must match a
single active variant or the line is rejected.
| Field | Meaning |
|---|---|
id |
Use as variant_id on order lines |
colour, size |
Option names; null when the product has no such option |
sku |
Your SKU from the web app, if set |
active |
Inactive variants cannot be ordered |
purchase_price, purchase_tax |
What one unit of this variant costs your account (at quantity 1) |
status, quantity |
Stock — see below |
status |
Meaning |
|---|---|
available |
Orderable now. quantity is the shelf count for stock-controlled designs, null for made-to-order variants that never run out |
preorder |
Nothing on the shelf, but the design accepts preorders |
out_of_stock |
Nothing on the shelf and no preorders |
unavailable |
The design, the product, or the variant is inactive |
Two prices
A design carries two prices, deliberately named apart:
| Field | Meaning |
|---|---|
retail_price |
The tax-inclusive price customers pay in your shops. Informational for the API — account orders are not charged this |
purchase_price / purchase_tax |
What your account pays Merchable per unit, ex tax, when it places an account order, plus the tax on it. This is what POST /v1/orders charges |
purchase_breakdown explains the purchase price: the product's base price for
your country, fees for placements beyond the ones the product includes (a back
print on top of the front, say), and any premium for the design's print method.
The design-level figure and the per-variant figures assume every placement on
the design is printed, including personalisation zones.
Both prices are null for gift cards and for designs that cannot be priced for
your account's country; such designs cannot be ordered.
Quantity tiers
Products may have quantity breaks. quantity_tiers lists them for your country,
lowest quantity first, with the ex-tax product price at each break. Tiers apply
to the summed quantity of a product across the whole order, not per line:
the tier that total reaches replaces product_base in every line's unit price,
while placement fees and the print method premium carry through unchanged. You
do not apply tiers yourself — POST /v1/orders/quote returns each line already
priced at the tier the order reaches.
Personalisation zones
A personalisation zone is a text area on the design that the customer's own words print into — a player's name across the back, a number on a sleeve. Zones are added to a design in the design studio (Add element → Zone), where the merchant sets the label, character limits, whether it is required, and the typography (font, weight, alignment, fill colour per light/dark garment).
A design with zones lists them under personalisation_zones, in display order:
| Field | Meaning |
|---|---|
id |
The zone's id. Use it as zone_id when filling the zone on an order line |
label |
What the customer is asked for, e.g. "Player name" |
placement |
The placement the text prints on, lower-cased (front, back, …); null if the zone is not on a placement |
required |
On a personalised line, this zone must be filled |
min_characters, max_characters |
Length limits enforced on text |
placeholder |
Example text shown in the shop |
Filling a zone on an order line
Send personalisation on the line, one entry per zone:
{ "design_id": "d8Xk2mQz", "colour": "Black", "size": "L", "quantity": 1,
"personalisation": [ { "zone_id": "4d1c8f2a-…", "text": "SMITH" } ] }
The rules POST /v1/orders and POST /v1/orders/quote apply:
- One line per distinct text. A personalised line is always its own order line,
even when another line has the same variant. Ten players means ten lines, each
with its
quantity(usually1). Lines withoutpersonalisationfor the same variant are merged as usual. - Every required zone must be filled on a personalised line. Optional zones may be omitted or sent with empty text, which leaves them unprinted.
textis trimmed and must be withinmin_characters–max_characters. Eachzone_idmay appear once per line and must belong to the design.- Font, weight, size and colour are not chosen per line — they follow the zone's settings in the design studio, with the fill colour picked for the garment's light or dark colour automatically.
Validation failures are 422 problems with the offending field in errors:
{
"status": 422,
"detail": "items.2.personalisation.0.text: must be at most 12 characters",
"errors": { "items.2.personalisation.0.text": ["must be at most 12 characters"] }
}
| Error | Cause |
|---|---|
items.N.personalisation.M.zone_id: is not a personalisation zone of this design |
Unknown or another design's zone |
items.N.personalisation.M.zone_id: is given more than once |
Duplicate zone on one line |
items.N.personalisation.M.text: is required |
Empty text on a required zone |
items.N.personalisation.M.text: must be at least/at most X characters |
Outside the zone's limits |
items.N.personalisation: zone "Player name" is required |
A required zone missing from a personalised line |
Ordering a zoned design without personalisation
A line with no personalisation orders the design with every zone empty: nothing
prints in the zone and, if the zone is the only element on its placement, that
placement is not charged. So a design whose back holds only a name zone costs
the back-print fee less when ordered plain. The design read's purchase prices
assume the zones are filled; the quote prices each line as sent.
What you get back
Every order item (and quote line) carries personalisation — zone_id,
label, placement and text — on GET /v1/orders/{id}, in the create and
quote responses, and inside every webhook payload, so your fulfilment records
match what was printed. Merchable renders the text into the print file once the
order is placed and checks it before production; contact support to correct a
typo on an order that has not yet entered production.