Stitchwork Docs
stitchwork.io

Core concepts

Products & catalogue

Your catalogue is the spine of everything personalised. Get SKUs, categories and product URLs right and the rest of Stitchwork works without further help.

Product shapes

Pick the type that matches how the thing is sold:

TypeUse whenVariants
simple One thing, one price. A notebook, a candle, a single guitar. One, created automatically, inheriting the parent SKU, price, currency and status.
configurable The customer chooses: size, colour, wood, finish. Generated from the option axes you declare.
variant Your export lists every variant as its own top-level row with no parent. Managed by hand or by the importer.

Status is draft, active or discontinued. Only active products are ranked or recommended — the single most common cause of an empty recommendations list is a catalogue that imported as drafts.

Fields that pull their weight

sku
The join key for everything: tracked events, imported order lines, external quote lines, category re-ranking. Up to 64 characters of letters, digits, dot, hyphen, underscore or slash. Unique per workspace when set.
web_url
The product's page on your site. It is how a bare page_view is matched to a product, and it is what product QR codes point at. Without it you can still rank — you just have to send SKUs on every intent event, and you cannot make a product QR.
category
Free text, one per product. Drives the category filter on recommendations, the category-interest endpoint, and category balancing. Worth populating even if it is coarse.
base_price / base_compare_price
The price and the "was" price. When base_compare_price sits above the effective price, the product counts as on sale and the on_sale ranking signal can boost it.
created_at
Settable on create and update. The newness ranking signal decays against it, so importing the real creation date from your source system means a five-year-old product doesn't rank as brand new the day you migrate.
external_source / external_id
Your system's identifier for this product. Store it and you can ask for recommendations by your own ids (?external_ids=) instead of ours.
tags, attributes
A string array and an arbitrary JSON blob. Carried through, not interpreted.

Options and variants

A configurable product declares its axes as options, then you generate the combinations.

# declare the axes
curl -X POST https://app.stitchwork.io/api/v1/products/12/options \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"name":"wood","display_name":"Wood","position":0,
       "values":[{"value":"mahogany"},{"value":"walnut"}]}'

curl -X POST https://app.stitchwork.io/api/v1/products/12/options \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"name":"size","display_name":"Body size","position":1,
       "values":[{"value":"O"},{"value":"OO"},{"value":"OOO"}]}'

# generate every combination
curl -X POST https://app.stitchwork.io/api/v1/products/12/generate-variants -b cookies.txt

The generator returns what it did:

{"version":"v1","data":{
  "created": 6,
  "discontinued": 0,
  "total": 6,
  "variants": [ … ]
}}

Option names must be 1–40 lowercase letters, digits or underscores (display_name is the pretty version shown to staff). Re-running the generator after changing an option is safe: new combinations are created, and combinations that no longer exist are marked discontinued rather than deleted — a variant that has appeared on a quote or an order must not vanish.

Editing a variant

Prices and statuses are per variant. Set them individually rather than through the parent:

curl -X PATCH https://app.stitchwork.io/api/v1/variants/88 \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"price":"2750.00","status":"active"}'

# and look one up by SKU when that's all you have
curl https://app.stitchwork.io/api/v1/variants/by-sku/CONCERTO-WAL-OO -b cookies.txt

Importing a catalogue

Products → Import, or POST /api/v1/products/import. Owner only. Up to 20 MB and 50,000 rows per file; the endpoint is allowed to run for up to ten minutes because being cut off mid-file is the worst possible outcome.

Three ways to send the file

# 1. multipart upload
curl -X POST https://app.stitchwork.io/api/v1/products/import \
  -b cookies.txt -F "file=@catalogue.csv"

# 2. JSON body with a URL we fetch, or inline CSV
curl -X POST https://app.stitchwork.io/api/v1/products/import \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"url":"https://example.com/exports/catalogue.csv"}'

# 3. raw CSV body
curl -X POST https://app.stitchwork.io/api/v1/products/import \
  -H "Content-Type: text/csv" -b cookies.txt --data-binary @catalogue.csv

Columns

Headers are matched case-insensitively and aliases are accepted, so most Shopify and Magento exports work unmodified. Only product_sku is required.

ColumnAliasesNotes
product_skusku, parent_skuRequired. Matched against product SKUs first, then variant SKUs.
product_namename, title1–200 characters.
product_typetypesimple / variant / configurable. virtual, bundle, downloadable, grouped, service and physical are coerced to simple. Blank becomes configurable if the row declares options, else simple.
descriptionFree text.
brandFree text.
categorycategoriesMagento paths are simplified: Default/Beds/All BedsBeds, and a comma-separated list takes the first path.
tagsComma-separated.
base_priceNumber.
base_compare_pricecompare_price, compare_at_price, rrp, was_price, list_price, msrpThe "was" price. Feeds the on-sale signal.
currency3-letter ISO code.
statusdraft / active / discontinued.
web_urlurl, product_url, url_keyThe product page on your site.
created_atcreated, created_date, date_createdThe source system's creation date. Read day-first for ambiguous formats.
variant_skuchild_skuMakes the row a variant of product_sku.
variant_pricepricePer-variant price.
variant_name, variant_statusPer-variant label and status.
option:<name>Repeatable. option:wood declares the wood axis and gives this row's value.
configurable_variationsMagento's inline child list: sku=A,size=L,color=Red|sku=B,size=M,color=Blue. Child rows named here are absorbed as variants rather than imported as standalone products.

What the importer guarantees

Reading the summary

{"version":"v1","data":{
  "rows_total": 1204,
  "rows_ok": 1198,
  "rows_failed": 6,
  "products_created": 310,
  "products_updated": 88,
  "variants_created": 806,
  "variants_updated": 92,
  "parents_grouped": 310,
  "variants_grouped": 806,
  "type_remapped": {"bundle": 4},
  "variants_unmatched": [ … ],
  "child_conflicts": [ … ],
  "skus_sanitised": 2,
  "errors":   [{"line": 47, "message": "product_sku is required."}],
  "warnings": [ … ]
}}
variants_unmatched
Rows that named a child SKU whose parent could not be resolved. Usually a typo in product_sku, or a child listed before its parent in a file with no configurable_variations.
child_conflicts
A child SKU claimed by two different parents. The first parent wins; fix the export.
type_remapped
Counts per source type that was coerced onto our enum — useful for spotting that half your catalogue arrived as bundle.
skus_sanitised
SKUs that contained characters we don't allow and were cleaned. Check them; a sanitised SKU no longer matches your source system.

Managing products over the API

# list, filtered
curl "https://app.stitchwork.io/api/v1/products?status=active&brand=Maison%20Aurelia&q=concerto" \
  -b cookies.txt

# one product, with its options and variants
curl https://app.stitchwork.io/api/v1/products/12 -b cookies.txt

# create
curl -X POST https://app.stitchwork.io/api/v1/products \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{
    "name": "Concerto Acoustic",
    "sku": "CONCERTO",
    "type": "configurable",
    "status": "active",
    "brand": "Maison Aurelia",
    "category": "Acoustic guitars",
    "base_price": "2500.00",
    "base_compare_price": "2999.00",
    "currency": "GBP",
    "web_url": "https://example.com/guitars/concerto",
    "external_source": "shopify",
    "external_id": "gid://shopify/Product/123",
    "tags": ["handmade","uk"]
  }'
Delete is discontinue

DELETE /api/v1/products/{id} sets the product's status to discontinued. It stops being ranked and recommended, but every quote, order and event that references it keeps working. There is no hard product delete — that would rewrite history.

A manual thumb on the scale. is_featured contributes the featured signal, which is weighted at 2.0 by default — the heaviest of the global weights.

curl -X PATCH https://app.stitchwork.io/api/v1/products/12/featured \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"featured": true}'

Owner or admin. See ranking signals.

Catalogue health checklist