{"schemaStatus":"published","endpoints":[{"path":"/shop-api/","methods":["GET"],"description":"This guide.","acceptsPost":false,"actions":[]},{"path":"/shop-api/products","methods":["GET"],"description":"The shop's products at the signed-in customer's prices, a page at a time.","acceptsPost":false,"actions":[]},{"path":"/shop-api/orders","methods":["GET","POST"],"description":"GET: the signed-in customer's own submitted orders. POST: place an order for them (safe to repeat with the same Idempotency-Key).","acceptsPost":true,"actions":[]},{"path":"/shop-api/orders/{id}","methods":["GET"],"description":"One of the signed-in customer's own orders.","acceptsPost":false,"actions":[]},{"path":"/shop-api/userinfo","methods":["GET"],"description":"Who the signed-in customer is: member id, name and email (scope shop/identity).","acceptsPost":false,"actions":[]},{"path":"/shop-api/authorize","methods":["GET","POST"],"description":"Customer sign-in and approval page (OAuth authorization endpoint).","acceptsPost":true,"actions":[]},{"path":"/shop-api/token","methods":["POST"],"description":"Exchange an authorization code or refresh token.","acceptsPost":true,"actions":[]},{"path":"/shop-api/.well-known/oauth-protected-resource","methods":["GET"],"description":"OAuth protected resource metadata of the API.","acceptsPost":false,"actions":[]},{"path":"/shop-api/.well-known/oauth-authorization-server","methods":["GET"],"description":"OAuth authorization server metadata (also at /shop-api/.well-known/openid-configuration).","acceptsPost":false,"actions":[]},{"path":"/shop-api/mcp/","methods":["GET","POST"],"description":"The shop MCP for AI assistants (its own guide at GET).","acceptsPost":true,"actions":[]}],"apiVersion":"1.4.52","version":2,"guide":{"description":"Download this website's products at a customer's own prices, place orders and read back their own orders.","title":"Sample Shop API","sample":true,"sections":[{"title":"Use your own website's /shop-api/","paragraphs":["This is a sample. The shop API and the shop MCP belong to each website and are used on that website's own domain, at the /shop-api/ path (for example https://www.your-website.example/shop-api/). Always use the /shop-api/ path of your own target website, never this platform website, so that your users sign in and trade in the correct environment. Do not connect to this website: it does not serve a shop for your customers. The guide below is general guidance only, and its addresses use a placeholder domain that you replace with your own."]},{"title":"What this is","paragraphs":["The shop API gives a customer of this website, such as a trade customer, access to the shop's catalogue from their own system, and lets them place orders and read their own orders back. Everything is seen as that customer sees it: their level and groups decide which products they can see, and every price is the one they would really pay (their price group, quantity breaks, sale prices and live discounts), in the currency they ask for.","The customer is the member who signed in. The website is the one that owns this domain. Neither is ever chosen by a request."]},{"links":[{"label":"Protected resource metadata","url":"https://www.your-website.example/shop-api/.well-known/oauth-protected-resource"},{"label":"Authorization server metadata","url":"https://www.your-website.example/shop-api/.well-known/oauth-authorization-server"}],"title":"Signing in (OAuth 2.1)","paragraphs":["Authorization code with PKCE (S256 only) for public clients. A person at the customer signs in once, with their own login for this website, and approves what the connection may do. The client is given an access token (one hour) and a refresh token that is used once and replaced every time it is used.","Authorization endpoint: https://www.your-website.example/shop-api/authorize. Token endpoint: https://www.your-website.example/shop-api/token (grant_type authorization_code with code, code_verifier, client_id and redirect_uri; or refresh_token with refresh_token and client_id). Clients are identified by a registered client_id or by a client metadata document (the client_id is an https address that serves it).","Send the access token as Authorization: Bearer access-token. Without a valid one the API answers 401 with WWW-Authenticate: Bearer resource_metadata=\"https://www.your-website.example/shop-api/.well-known/oauth-protected-resource\".","The same sign-in serves the shop's MCP (https://www.your-website.example/shop-api/mcp/). A connection is for one website, and for one client."]},{"title":"Scopes","paragraphs":["The person approving chooses the scopes. A request without the scope it needs is refused with HTTP 403 and WWW-Authenticate: Bearer error=\"insufficient_scope\"."],"table":{"columns":["Scope","What the person is told","Used by"],"rows":[["shop/browse","Look at the products, categories, currencies, booking times and delivery prices, with your prices","GET https://www.your-website.example/shop-api/products"],["shop/orders:read","See your past orders","GET https://www.your-website.example/shop-api/orders, GET https://www.your-website.example/shop-api/orders/{id}"],["shop/orders:submit","Place orders for you","POST https://www.your-website.example/shop-api/orders"],["shop/identity","Tell the application who you are: your member number, name and email address","GET https://www.your-website.example/shop-api/userinfo"]]}},{"title":"Products","paragraphs":["GET https://www.your-website.example/shop-api/products returns one page of products as JSON: page, currency, tax (what the prices mean: the rate, and whether the shop shows prices with tax included), generated_at, returned, products and, when there is more, next_cursor. Repeat the request with cursor set to next_cursor until it is not returned.","Each product has id, code, title, url, description (the whole text, without markup), image, minimum_quantity, in_stock, can_buy, stock_message, a price block (currency, unit_price_excl_tax, unit_price_incl_tax and, on a discounted price, was_price_excl_tax, discount_percent and promotion) or price_hidden when this customer may not see the price, and flags has_variants, has_options and has_customisation. Variants are listed inside their product with their own id, code, colour, size, style, stock and price: a variant's id is what an order names.","Only products this customer may see and the shop sells are listed, in order of id. A product that is added while you download may or may not be in that download: the next one with updated_since will have it.","For a repeat download send updated_since with the generated_at of the last download that finished. A change to prices through the shop's price groups or discounts does not change a product's own record, so also download everything now and then (weekly, say) to pick those up."],"table":{"columns":["Parameter","Meaning","Value"],"rows":[["page","The shop page to list. Default: the website's main shop page.","number"],["currency","A currency the shop offers (its own, or one it has an exchange rate for). Default: the shop's own currency.","code, for example NZD"],["limit","Products on the page. Default 100, at most 200.","number"],["cursor","The next_cursor of the page before. Leave out for the first page.","text, from the previous page"],["updated_since","Only products changed at or after this time (the product, or one of its variants). Use the generated_at of your last complete download.","ISO 8601 time with its zone, or a date"],["variants","false leaves out the variants (colours, sizes, styles) inside each product. Default true.","true or false"]]},"example":"GET https://www.your-website.example/shop-api/products?limit=100&updated_since=2026-10-01T00:00:00Z\nAuthorization: Bearer access-token"},{"title":"Placing an order","paragraphs":["POST https://www.your-website.example/shop-api/orders with a JSON body (at most 100000 bytes) and an Idempotency-Key header places an order in one call: it is made, priced by the shop's own calculation for this customer, checked and submitted. The merchant reviews and confirms submitted orders; nothing here approves one. Payment is left to the terms the merchant has with the customer; an order that owes something carries a payment_url.","201 with result submitted and the order (order_id, status, lines, totals, delivery, payment_url when it applies). The same request sent again with the same key, for example after a timeout, gives 200 with replayed true and the same order, never a second one.","422 order_not_valid says what to fix in errors (an unavailable product, an option or answer still to choose, a delivery option to choose, a minimum spend); nothing is submitted, and the same request, changed, is sent again with the same key and revises the same draft. 409 price_changed: the total is not the expected_total; the answer has the order and its new total. 422 idempotency_key_reused: that key already placed a different order. 409 request_in_progress: the same key is still being processed; try again.","Customers can see and order only what the shop shows them: a product they may not see or buy is refused. A customer makes at most 10 orders a minute."],"table":{"columns":["Field","Meaning","Value"],"rows":[["Idempotency-Key (header)","Required. Your own id for this order: 1 to 100 letters, digits or . _ : ~ -. The same key always gives the same order.","text"],["items","Required. The lines: [{product_id, quantity, colour, size, style, answers}]. product_id is the id of a product or of one of its variants, as the product feed gives it; answers are for products with customisation questions. At most 100 lines.","list"],["expected_total","Advised. The total you were quoted. If the price is not that, nothing is submitted.","number"],["purchase_order","Your reference for the order (up to 100 characters), shown to the merchant.","text"],["notes","A note for the merchant (up to 2000 characters).","text"],["delivery_address","Where to deliver, if not your saved delivery address: any of name, company, email, phone, address, suburb, city, state, postcode, country. Changing it changes the delivery options and their price.","object"],["billing_address","The customer's billing details, if not your saved ones. Same fields.","object"],["shipping_option_id","The delivery option to use. Needed when the shop offers more than one for the address: the answer lists them (shipping_options) and says to choose.","number"],["currency","A currency the shop offers. Default: the shop's own.","code"],["promo_code","A promotion code.","text"],["page","The shop page. Default: the page of the products.","number"],["booking","{start, duration_minutes or end, resource_id} for an order of bookable products. Times are the merchant's local time without a zone.","object"]]},"example":"POST https://www.your-website.example/shop-api/orders\nAuthorization: Bearer access-token\nIdempotency-Key: PO-20481\nContent-Type: application/json\n\n{\"items\":[{\"product_id\":200,\"quantity\":12},{\"product_id\":341,\"quantity\":2,\"size\":\"L\"}],\"purchase_order\":\"PO-20481\",\"expected_total\":418.50,\"delivery_address\":{\"name\":\"Outlet Ltd\",\"address\":\"12 High St\",\"city\":\"Nelson\",\"postcode\":\"7010\",\"country\":\"NZ\"}}"},{"title":"Reading orders","paragraphs":["GET https://www.your-website.example/shop-api/orders lists the customer's own submitted orders, newest first, and GET https://www.your-website.example/shop-api/orders/{order_id} reads one (a cancelled one too). Nobody else's orders are ever returned, and a draft that has not been submitted is not listed. The list answers total_matches, returned, offset, orders and, when there are more, more.","An order is what the customer sees on their own order pages: order_id, invoice_number, status, the dates (created, submitted_on, cancelled_on, shipped_on, paid_on), currency, total_weight_grams, purchase_order, promo_code, notes, payment_method, tracking (reference, url, notes), totals (total, tax, shipping, discount, savings, vouchers, paid, refunds, balance_due and, when they apply, payment_fee, surcharge, adjustments, deposit and points), payment_url when something is still owing, the billing and delivery details, items (code, title, colour, size, style, quantity, unit price, discounts, line total, tax and total) and bookings. It never shows the merchant's costs, notes or freight fees."],"table":{"columns":["Parameter","Meaning","Value"],"rows":[["limit","Orders on the page. Default 10, at most 50.","number"],["offset","Orders to skip, newest first. Use the offset the more message gives.","number"],["since","Only orders submitted on or after this day.","date, yyyy-mm-dd"],["until","Only orders submitted on or before this day.","date, yyyy-mm-dd"],["include_items","false leaves out the lines (and bookings) of each order. Default true.","true or false"],["include_cancelled","true also lists cancelled orders. Default false.","true or false"]]},"example":"GET https://www.your-website.example/shop-api/orders?since=2026-10-01&limit=20\nAuthorization: Bearer access-token"},{"title":"Who is signed in (identity)","paragraphs":["An application that keeps its own records for a customer, such as saved conversations in its own database, can ask this website who the customer is instead of building its own login. Have the customer connect with the scope shop/identity (they are shown \"Tell the application who you are: your member number, name and email address\" and can decline it), then call GET https://www.your-website.example/shop-api/userinfo with the access token.","The answer is the customer the token was issued to, so it is only as trustworthy as the token: the customer signed in to this website's own login (password and two-factor where they use it) to approve the connection. Never accept a member id sent by a browser, a page or a request parameter as proof of who someone is. Always call userinfo from your server with the token, and key the customer's records by id. If reference is present you may use it to link the customer to a record you already hold under your own account reference, then store id against that record; do not rely on reference alone.","Access tokens last one hour; renew them with the refresh token (see Signing in) and call userinfo again. A customer who revokes the connection, or whose account is closed, gets 401 invalid_token at the next call, so you can stop serving their records. Logging out of the website does not end the connection.","Only these fields are given. Profile details, level, groups and price group are never part of it. This is not a full OpenID Connect provider: there is no ID token, and the connection is not a login session. The answer is sent with Cache-Control: no-store and is not kept in the website's request log. It is limited to 60 requests a minute for each customer, four at a time, with RateLimit-Limit and RateLimit-Remaining."],"table":{"columns":["Field","Meaning","Value"],"rows":[["id","The customer's id: a number that belongs to this customer alone and never changes. Use it as the key for the customer's own records.","number, for example 4821"],["sub","The same id as text. It is there because OpenID Connect clients and libraries look for sub; use id if you are not using one.","text, for example 4821"],["name","The customer's name as the website shows it. Left out when the customer has none.","text"],["email","The customer's login email address. Left out when the customer has none.","text"],["reference","A reference the business may have entered for this customer, such as the customer's account reference in your own system. Many customers have none, in which case it is left out. It is free text and is not checked for uniqueness, so two customers could carry the same one: use it only as a hint to find a record you already hold, never as the key.","text, for example C-1042"]]},"example":"GET https://www.your-website.example/shop-api/userinfo\nAuthorization: Bearer access-token\n\n200 {\"id\":4821,\"sub\":\"4821\",\"name\":\"Anna Smith\",\"email\":\"anna@example.com\",\"reference\":\"C-1042\"}\n403 insufficient_scope when the customer did not allow shop/identity; 401 invalid_token when the token has expired or the connection was revoked; 503 when it cannot be read just now (try again)."},{"title":"Errors and limits","paragraphs":["400 a parameter is not valid (the message says which), 401 sign in again, 403 the scope was not allowed, 404 there is no such order, 429 too many requests (see Retry-After), 503 try again shortly. Errors are JSON: {\"error\": code, \"message\": text}. The product list and reading orders are each limited to 20 requests a minute for each customer, two at a time; RateLimit-Limit and RateLimit-Remaining are sent with each answer.","The shop MCP, at https://www.your-website.example/shop-api/mcp/, reads orders too (get_orders) and can place them."]}]}}