Skip to main content

Catalogue

What the shop sells, and everything a storefront needs to show it. Every call takes the X-Shop-Key header (see Authentication) and answers only with what the shop has on sale: a product that is hidden, in the trash, or cannot be made right now is not in any list and answers 404 on its own.

Endpoints​

MethodPathDescription
GET/v1/shop/productsA page of products, with the filters and sort orders the listing offers
GET/v1/shop/products/{slug}One product, with its variants and the documents the form widget needs
GET/v1/shop/categoriesEvery category, in the shop's order
GET/v1/shop/languagesThe languages the shop shows, its main language first
GET/v1/shop/showcaseThe designs shown in each place of the storefront
GET/v1/shop/searchProducts matching a search
GET/v1/shop/shipping-methodsThe delivery options, their prices and the VAT rate

List products​

curl "https://shop-api.typograph.nl/v1/shop/products?category=birth&theme[]=Birth&sort=price_asc" \
-H "X-Shop-Key: shop_pk_…" \
-H "Accept-Language: en"
ParameterDescription
limit, offsetThe window, see Response shapes
categoryA category slug. An unknown one is a 404, not an empty list
sortrecommended (the default: the order the shop set in its back office), popular, price_asc or price_desc. Anything else is read as recommended
one per filterThe values to filter on, named after the filter: theme[]=Birth&theme[]=Christmas. Values of one filter are OR'ed, different filters are AND'ed. A filter that does not exist is ignored
{
"data": [
{
"slug": "welcome",
"name": "Welcome",
"badge": "New",
"tags": { "theme": ["Birth"], "colour": ["Blue"] },
"from_price_cents": 2450,
"preview_image_url": "https://shop-api.typograph.nl/designs/…/thumbnail",
"is_personalizable": true,
"categories": ["birth"]
}
],
"meta": {
"total": 1,
"limit": 25,
"offset": 0,
"has_more": false,
"sort": "price_asc",
"sorts": [
{ "value": "recommended", "label": "Recommended" },
{ "value": "popular", "label": "Popular" },
{ "value": "price_asc", "label": "Price, low to high" },
{ "value": "price_desc", "label": "Price, high to low" }
],
"filters": { "theme": ["Birth"], "colour": [], "prijs": [] },
"facets": [
{
"axis": "theme",
"label": "Theme",
"values": [
{ "value": "Birth", "name": "Birth", "count": 1, "is_selected": true },
{ "value": "Christmas", "name": "Christmas", "count": 0, "is_selected": false }
]
}
]
}
}
FieldDescription
badgeA short label the shop puts on the product's card, or null
tagsThe filter values this product carries, per filter
from_price_centsThe price of the cheapest variant, excluding VAT
preview_image_urlThe shop's photograph of the product, or else the thumbnail of its design; null when there is neither
is_personalizableWhether the product is filled in with the form widget
categoriesCategory slugs

Filters​

The filters are the shop's own: the shopkeeper makes them under Categories and filters. So do not hard-code them. meta.facets lists every filter with every value, in the order the shop set, and the same filters come back on every listing, also when a value matches nothing. That keeps a filter sidebar from rearranging itself between pages.

  • axis is what goes in the query string; label is the filter's name to show.
  • value is what goes in the query string; name is how it reads.
  • count is how many products ticking this value would leave, given the other filters already chosen. It says what happens next, not what the category holds.
  • is_selected says whether the request asked for it. meta.filters repeats the choice per filter.

The last filter is always prijs (it keeps its Dutch name), worked out from the product's price rather than set by hand, so it always agrees with what the product costs.

meta.sorts is the list of sort orders, with labels in the request's language. Show this list rather than a copy of your own.

Get one product​

curl https://shop-api.typograph.nl/v1/shop/products/welcome \
-H "X-Shop-Key: shop_pk_…"

Everything in the list entry, plus:

{
"data": {
"slug": "welcome",
"name": "Welcome",
"description": "Birth announcement with room for a photo.",
"print_specification": { "paper": "320 g handmade board", "format": "105 x 148 mm" },
"variants": [
{ "id": "0198a3c4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "name": "25 cards", "sku": "WEL-25", "price_cents": 2450 }
],
"form_url": "https://shop-api.typograph.nl/products/welcome/form.json",
"template_url": "https://shop-api.typograph.nl/products/welcome/template.json",
"manifest_url": "https://shop-api.typograph.nl/products/welcome/manifest.json",
"asset_base_url": "https://shop-api.typograph.nl/products/welcome/"
}
}
FieldDescription
print_specificationWhat it is printed on, as label and value, or null
variantsWhat can be ordered. Send a variant's id with an order line
form_url, template_url, manifest_urlThe form, the design and its fonts and images, for the form widget
asset_base_urlWhat the design's relative paths resolve against

The four addresses are on the shop itself, so the widget reads them without a Typograph account. They answer only the websites on the shop's allowed list (see The product documents).

List categories​

curl https://shop-api.typograph.nl/v1/shop/categories \
-H "X-Shop-Key: shop_pk_…"
{
"data": [
{ "slug": "birth", "name": "Birth" },
{ "slug": "christmas", "name": "Christmas" }
],
"meta": { "total": 2, "limit": 100, "offset": 0, "has_more": false }
}

A category's slug stays the same when the shopkeeper renames it, so it is safe to put in your URLs.

List languages​

curl https://shop-api.typograph.nl/v1/shop/languages \
-H "X-Shop-Key: shop_pk_…"
{
"data": [
{ "code": "en", "name": "English", "is_default": true },
{ "code": "nl", "name": "Nederlands", "is_default": false }
],
"meta": { "total": 2, "limit": 100, "offset": 0, "has_more": false }
}

Only the languages the shopkeeper has made visible, the main one (is_default) first. Offer these in your language menu and send the chosen code as Accept-Language. name is the language's own name, as the shopkeeper wrote it.

Showcase​

curl https://shop-api.typograph.nl/v1/shop/showcase \
-H "X-Shop-Key: shop_pk_…"

The designs a storefront shows in its fixed places, already topped up: for each place, the designs the shopkeeper chose in the back office that are on sale, in the chosen order, then the first designs of the catalogue order until the place is full. With nothing chosen, a place is simply the catalogue order. Each design has the same shape as a row of the product list.

{
"data": {
"home_banner": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"home_carousel": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"cart_suggestions": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"faq": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"sign_in": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"register": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }],
"contact": [{ "slug": "welcome", "name": "Welcome", "from_price_cents": 2450, "…": "…" }]
}
}

The places and the most designs each shows: home_banner 3, home_carousel 12, cart_suggestions 4, and faq, sign_in, register and contact 1. They are fixed in the service: a new place needs a storefront that renders it.

curl "https://shop-api.typograph.nl/v1/shop/search?query=blue+birth" \
-H "X-Shop-Key: shop_pk_…"

Every word has to match, so typing more narrows the result. A word matches a product's name, its description or the name of one of its categories. Only the first six words count, an empty query finds nothing, and a search finds at most 60 products.

The answer is a page of products like /v1/shop/products, with limit and offset, but without the filters and sort orders in meta.

Delivery options​

curl https://shop-api.typograph.nl/v1/shop/shipping-methods \
-H "X-Shop-Key: shop_pk_…" \
-H "Accept-Language: en"
{
"data": [
{ "value": "next_day", "label": "Delivered tomorrow before 12:00", "price_cents": 895, "vat_rate": 21 },
{ "value": "standard", "label": "Standard delivery, Thursday", "price_cents": 0, "vat_rate": 21 },
{ "value": "pickup", "label": "Collect from the warehouse", "price_cents": 0, "vat_rate": 21 }
],
"meta": { "total": 3, "limit": 100, "offset": 0, "has_more": false }
}

Show these, and send the chosen value as shipping_method with the order. price_cents excludes VAT; vat_rate is the percentage charged over the goods and the delivery together. Read them here rather than copying them into your storefront: the order is charged at the shop's prices, whatever your page said.