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
| Method | Path | Description |
|---|---|---|
GET | /v1/shop/products | A 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/categories | Every category, in the shop's order |
GET | /v1/shop/languages | The languages the shop shows, its main language first |
GET | /v1/shop/showcase | The designs shown in each place of the storefront |
GET | /v1/shop/search | Products matching a search |
GET | /v1/shop/shipping-methods | The 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"
| Parameter | Description |
|---|---|
limit, offset | The window, see Response shapes |
category | A category slug. An unknown one is a 404, not an empty list |
sort | recommended (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 filter | The 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 }
]
}
]
}
}
| Field | Description |
|---|---|
badge | A short label the shop puts on the product's card, or null |
tags | The filter values this product carries, per filter |
from_price_cents | The price of the cheapest variant, excluding VAT |
preview_image_url | The shop's photograph of the product, or else the thumbnail of its design; null when there is neither |
is_personalizable | Whether the product is filled in with the form widget |
categories | Category 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.
axisis what goes in the query string;labelis the filter's name to show.valueis what goes in the query string;nameis how it reads.countis 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_selectedsays whether the request asked for it.meta.filtersrepeats 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/"
}
}
| Field | Description |
|---|---|
print_specification | What it is printed on, as label and value, or null |
variants | What can be ordered. Send a variant's id with an order line |
form_url, template_url, manifest_url | The form, the design and its fonts and images, for the form widget |
asset_base_url | What 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.
Search
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.