Skip to main content

Webshop API

A Typograph webshop sells personalised print products: a visitor fills in a design with the Form Widget, and orders it as a printed product. The shop itself — its catalogue, its orders and its back office — runs as its own service, and every storefront reaches it over one API, /v1/shop.

The storefront is yours to build. Our own demo storefront, Stamped at shop.typograph.nl, uses exactly the calls on these pages and nothing else, so whatever it can do, yours can too.

PartWhat it isPage
CatalogueProducts, categories, filters, search, delivery optionsCatalogue
Personalisation and ordersThe form widget, picture uploads, placing an order and reading it backPersonalise and Order
CustomersOptional accounts, a shortlist and order historyCustomers

The full specification is in the Webshop API Reference.

Not the platform API​

The webshop is a separate service from the Typograph API. It does not sit behind api.typograph.nl, it takes no OAuth token, and the gateway's rate limits and scopes do not apply to it.

Each shop has its own address. The examples on these pages use the demo shop:

https://shop-api.typograph.nl

Replace that with your own shop's address. The paths are the same for every shop.

Authentication​

Every call carries the shop's publishable key in X-Shop-Key:

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

The shop owner finds the key in the back office under Settings → Connection, in the Shop key card (see Connection).

Publishable means it belongs in your page. The visitor's own browser places the order, so the key has to be readable there. It names the shop; it does not prove who is asking.

What stops someone from copying the key into their own site is the Allowed websites list under it. A browser always says which site a request comes from (the Origin header) and cannot lie about it, so the shop answers only the sites on that list:

RequestAnswer
No X-Shop-Key, or the wrong one401
Right key, from a site on the listServed, with Access-Control-Allow-Origin for that site
Right key, from a site not on the list401
Right key, no Origin at all (your own server)Served

The list is empty when a shop starts, which means no website may use the key yet. Add every origin your storefront runs on, one per line, as scheme and host without a path: https://kaarten.nl, and https://www.kaarten.nl separately if you use both.

Creating a new key in the back office stops the old one at once: every site that uses it gets 401 until it has the new one.

What a browser may send​

A page on a site on the list may make every call from the browser. The shop answers cross-origin GET, POST, PUT and DELETE requests with the headers Content-Type, X-Shop-Key, X-Customer-Token, X-Order-Token and Accept-Language, and lets the browser read the Location and Retry-After headers of a response. That covers the catalogue, the picture uploads, placing and reading back an order, and the customer calls.

Your own server may make the same calls; without an Origin the list does not apply.

The product documents​

The form widget loads a product's form, template, manifest, fonts and pictures from the shop itself (see Personalise and Order). Those addresses take no key, but they answer the same list: a page on a site that is not on it gets 403. A request without Origin, such as a plain <img> or your own server, is served.

Picture uploads​

At checkout, the visitor's browser uploads pictures straight to the shop's file storage, not to the API. That storage keeps its own list of sites that may upload. Ask Typograph support to add your storefront's origin before you go live; until then the uploads from your site are refused by the browser.

Response shapes​

The shapes are the ones the platform's own services use.

A single resource sits under data:

{
"data": { "slug": "welcome", "name": "Welcome" }
}

A collection is data plus a meta:

{
"data": [{ "slug": "birth", "name": "Birth" }],
"meta": { "total": 1, "limit": 100, "offset": 0, "has_more": false }
}
  • Paging. The product listing and search take limit and offset: limit is 25 when you send none, and at most 100. Categories and delivery options come back whole; a customer's order history comes back in one list of at most 100 orders.
  • Prices are whole cents, excluding VAT: 2450 is € 24,50. The shop adds VAT at checkout, over the goods and the delivery together.
  • JSON is snake_case.
  • Language. Send Accept-Language with the visitor's language. The shop's own words — product, variant, category and filter names, badges and descriptions — come back in any language the shop lists at GET /v1/shop/languages and has translated; a text that is not translated yet comes in the shop's main language. Texts the shop writes for people — the names of the sort orders and the delivery options, and most error descriptions — come in en or nl; anything else gets English. Filter values in a request stay in the main language, whatever language the labels are shown in.

Errors​

A refusal has the same shape as on the platform:

{
"error": "not_found",
"error_description": "This product does not exist."
}

Match on error, which does not change. error_description is written to be shown to a visitor as it is.

StatuserrorWhen
401unauthorizedNo key, the wrong key, or a site not on the allowed list
401not_signed_inA customer call without a valid X-Customer-Token
404not_found, or the code its page namesAn unknown product, category or order, or an order read with the wrong token. A product saved to the shortlist that does not exist answers unknown_product (see Customers)
422see the page of the callThe request was understood and refused; nothing was stored
429too_many_requestsToo many uploads or feedback messages from one visitor, or too many sign-in attempts for one e-mail address, see below

The 422 codes are listed with the call that returns them: uploads, orders and customers.

Rate limits​

Three calls are limited. Over a limit they answer 429 too_many_requests with a Retry-After header in seconds.

CallLimitCounted per
POST /v1/shop/uploads30 requests per hourVisitor, by IP address
POST /v1/shop/sessions5 attempts per 15 minutesE-mail address, known or not; a successful sign-in clears it
POST /v1/shop/feedback10 per hourVisitor, by IP address

A checkout normally asks for uploads once. Sign-in is counted per e-mail address rather than per visitor because a storefront that signs customers in from its own server would otherwise make them all share one budget. The price is that anyone who knows an address can keep its owner out for a while by spending the five tries.

The other calls have no limit of the shop's own.