# Sepetak > Sepetak is a hosted storefront platform for Indonesian small shops. Each shop has a public storefront API at https://.sepetak.com/api/v1; a merchant's own site (headless) calls it from a domain registered to the shop. No API key. ## Guide - [Access](https://docs.sepetak.com/guide/access.md): No API key. A server may read anything; a browser needs a registered domain and a plan that covers the method. - [Checkout in five calls](https://docs.sepetak.com/guide/checkout.md): The server prices the cart; the buyer confirms with a one-time code; you receive track_token once. - [Errors](https://docs.sepetak.com/guide/errors.md): One envelope for every error; message is Indonesian and safe to show. - [Limits and buyer links](https://docs.sepetak.com/guide/limits.md): Code budgets, per-IP limits, idempotent retries, and where buyer emails point. - [Quickstart](https://docs.sepetak.com/guide/quickstart.md): One fetch against the catalogue, pinned to an API version. - [Versions](https://docs.sepetak.com/guide/versions.md): Each API version is a date. Old versions stay served; X-API-Version picks one. ## API reference - [Every operation and field, current version 2026-09-09](https://docs.sepetak.com/api/reference.md) - [OpenAPI 3.1 document](https://sepetak.com/openapi.json): `?version=YYYY-MM-DD` for an older shape; `info.x-versions` lists them ## Other - [Changelog](https://docs.sepetak.com/changelog.md): What each API version changed. Generated from the OpenAPI documents. - [JavaScript client](https://www.npmjs.com/package/@sepetakhq/storefront-kit): the fetch layer the hosted templates use --- # Sepetak storefront API Build your own storefront on a Sepetak shop. Your site calls the same API the hosted templates use. - **Base URL:** `https://.sepetak.com/api/v1` (any host the shop answers on) - **Spec:** [`https://sepetak.com/openapi.json`](https://sepetak.com/openapi.json), OpenAPI 3.1, one document per version (`?version=YYYY-MM-DD`) - **Plan needed:** Tumbuh to read, Skala to check out ([Access](/guide/access)) - **For machines:** [`/llms.txt`](pathname:///llms.txt); every page also exists as `.md` at the same path ## Using AI? Hand it this ## Where to start 1. [Access](/guide/access): there is no key; your origin and your plan decide. 2. [Quickstart](/guide/quickstart): one `fetch`. 3. [Checkout in five calls](/guide/checkout): the sequence and the rules the server enforces. 4. [API reference](/api): every route and field, rendered from the spec. --- {/* GENERATED by scripts/changelog.mjs from specs/*.json. Edit the platform release list, not this file. */} # Changelog Each API version is a date. Pin one with `X-API-Version`; an unpinned request gets the baseline. Old versions stay served. See [Versions](/guide/versions). ## 2026-09-09 (current) - checkout draft requires a buyer phone Reference: [2026-09-09](/api) (pick it in the document switcher) · [openapi.json](https://sepetak.com/openapi.json?version=2026-09-09) ## 2026-09-05 - Baseline. The shape every unpinned request receives; it stays fixed. Reference: [2026-09-05](/api) (pick it in the document switcher) · [openapi.json](https://sepetak.com/openapi.json?version=2026-09-05) --- # Access There is no API key. The shop is the host you call. | From | Rule | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | A server | Open. Every `GET` works without setup. | | A browser | Your page's origin must be a domain registered to the shop, and the plan decides: **Tumbuh** allows `GET`, **Skala** allows everything. | Sepetak registers your domain for now: send the domain name from the shop owner's account. Send `credentials: 'omit'` on every request. Cookies do not travel cross-origin, and the browser refuses a request that asks for them. ## What a refusal looks like A cross-origin request outside your plan answers `403` with code `plan_limit`. An origin that is not one of the shop's domains answers `403 forbidden`. Both carry CORS headers, so your page can read them. See [Errors](/guide/errors). --- # Checkout in five calls The server prices the cart; your site sends `product_id` and `qty`, nothing more. The buyer confirms their contact with a one-time code on every order. | Call | Does | Returns | | --------------------------- | --------------------------------------------- | ----------------------------------- | | `POST /shipping/rates` | quotes for the cart and the destination | `shipping_quote_id` | | `POST /checkout/draft` | turns the cart into a priced draft | `draft_id`, `total`, `otp_required` | | `POST /checkout/otp/send` | sends a code to the buyer's email or WhatsApp | `expires_at` | | `POST /checkout/otp/verify` | checks the code and places the order | `order`, `payment`, `track_token` | | `GET /orders/track/{token}` | status and payment instructions | `order.timeline`, `payment` | The [API reference](/api) has the fields of every call. ## Rules the server enforces - You receive `track_token` once. Keep it. - Render `order.timeline` and `order.status_label` as given; the merchant or a gateway can move the status at any time. - Branch on `payment.kind`. The server decides which gateway fulfils a method, so `payment.method` can change under you. - Send a code when the buyer asks for one, and show a refused code as refused; the server does not say why. --- # Errors Every error is `{ "error": { "code", "message", "details" } }`. `message` is Indonesian and safe to show to the buyer. ```json { "error": { "code": "plan_limit", "message": "Akses API belum termasuk paket toko ini.", "details": {"quota": "api_access", "plan": "tumbuh", "need": "write"} } } ``` | Status · code | Meaning | | ----------------------------- | --------------------------------------------------------------------------------------- | | `403 plan_limit` | Your plan does not cover this cross-origin request; `details.need` is `read` or `write` | | `403 forbidden` | Your origin is not one of the shop's domains | | `400 unsupported_api_version` | Unknown date; `details.supported` lists the served ones | | `400` on a code | Wrong, expired or too many tries; the server gives one answer for all three | | `409` | The order already moved; reload it from `/orders/track/{token}` | --- # Limits and buyer links - Codes: five per contact per hour. Some routes have a per-IP budget, so call checkout from the buyer's browser rather than through a proxy; then each buyer counts as one. - Retry a `POST` with `Idempotency-Key`. A replayed answer carries `Idempotent-Replay`. - Confirmation emails and gateway returns point at the shop's **primary domain**. To land buyers on your site, make your domain primary and serve `/track/{token}` there. - A plan change reaches the API within a minute. --- # Quickstart ```js const BASE = 'https://.sepetak.com/api/v1'; const res = await fetch(`${BASE}/products?limit=12`, { headers: {'X-API-Version': '2026-09-09'}, credentials: 'omit', }); const {products} = await res.json(); ``` Pin `X-API-Version` to the date you wrote against; that shape stays fixed. Without the header you get the baseline, `2026-09-05`. See [Versions](/guide/versions). For a JavaScript site, the client our templates use ships as [`@sepetakhq/storefront-kit`](https://www.npmjs.com/package/@sepetakhq/storefront-kit): call `configureApi({ baseUrl })`, then each endpoint is one function with one error type. --- # Versions Each API version is a date. Old versions stay served; `X-API-Version` picks one. An unpinned request gets the baseline, which stays fixed. - [Changelog](/changelog): what each version changed. - [Reference](/api): the current version opens first; pick an older one in the document switcher. - Spec for any version: `https://sepetak.com/openapi.json?version=YYYY-MM-DD`. `info.x-versions` lists them all. We change a response shape by cutting a new date. Under a date that has shipped, fields keep their names and types; we add, we do not rename or remove. --- # Sepetak storefront API 2026-09-09 The storefront API. The shop is the request's Host. Every field may be null; the schema shows each field's type when present. Errors are {error:{code,message,details}}. Send X-API-Version to pin the shape you were written against. Base URL: `https://.sepetak.com/api/v1`. Every error is the `Error` schema below. ## GET /categories List categories Response 200: - `categories[].image`: string - `categories[].image_srcset`: string - `categories[].key`: string - `categories[].label`: string - `categories[].material_spec`: string - `categories[].spec_label`: string - `categories[].subtitle`: string ## POST /checkout/draft Create draft Request: - `address`: string - `city`: string - `contact`: string - `courier_code`: string - `customer_name`: string - `items[].product_id`: string - `items[].qty`: integer - `items[].variant_id`: string - `payment_method`: string - `phone`: string - `shipping_quote_id`: string - `voucher_code`: string Response 200: - `contact.channel`: string - `contact.masked`: string - `discount_total`: integer - `draft_id`: string - `expires_at`: string (date-time) - `free_shipping`: boolean - `lines[].image`: string - `lines[].image_srcset`: string - `lines[].line_total`: integer - `lines[].name`: string - `lines[].preorder`: boolean - `lines[].product_id`: string - `lines[].promotion_name`: string - `lines[].qty`: integer - `lines[].sku`: string - `lines[].undiscounted_unit_price`: integer - `lines[].unit_price`: integer - `lines[].unlimited_stock`: boolean - `lines[].variant_id`: string - `lines[].variant_label`: string - `otp_required`: boolean - `payment_fee`: integer - `preorder_lead_days`: integer - `promotion_name`: string - `shipping_cost`: integer - `shipping_discount`: integer - `shipping_promotion_name`: string - `subtotal`: integer - `total`: integer - `unique_code`: integer - `voucher.amount`: integer - `voucher.applied`: boolean - `voucher.code`: string - `voucher.message`: string - `voucher.outbid`: integer - `voucher.outbid_name`: string - `voucher.reason`: string ## POST /checkout/otp/send Send code Request: - `draft_id`: string Response 200: - `channel`: string - `expires_in_seconds`: integer - `masked`: string - `sent`: boolean ## POST /checkout/otp/verify Verify and place Request: - `code`: string - `draft_id`: string Response 200: - `order.awb`: string - `order.buyer_claimed_at`: string (date-time) - `order.city`: string - `order.contact_masked`: string - `order.courier_code`: string - `order.customer_name`: string - `order.discount_total`: integer - `order.expires_at`: string (date-time) - `order.id`: string - `order.lines[].image`: string - `order.lines[].image_srcset`: string - `order.lines[].line_total`: integer - `order.lines[].name`: string - `order.lines[].preorder`: boolean - `order.lines[].qty`: integer - `order.lines[].sku`: string - `order.lines[].undiscounted_unit_price`: integer - `order.lines[].unit_price`: integer - `order.lines[].unlimited_stock`: boolean - `order.lines[].variant_label`: string - `order.number`: string - `order.payment_fee`: integer - `order.payment_method`: string - `order.placed_at`: string (date-time) - `order.preorder_lead_days`: integer - `order.preorder_ship_estimate`: string (date-time) - `order.promotion_name`: string - `order.shipping_cost`: integer - `order.shipping_discount`: integer - `order.shipping_promotion_name`: string - `order.status`: string - `order.status_label`: string - `order.subtotal`: integer - `order.timeline[].done`: boolean - `order.timeline[].key`: string - `order.timeline[].label`: string - `order.total`: integer - `order.unique_code`: integer - `order.voucher_code`: string - `payment.amount`: integer - `payment.expires_at`: string (date-time) - `payment.kind`: string - `payment.manual_account_holder`: string - `payment.manual_account_number`: string - `payment.manual_bank`: string - `payment.manual_qr_image_url`: string - `payment.manual_whatsapp_phone`: string - `payment.method`: string - `payment.qr_image_url`: string - `payment.qr_payload`: string - `payment.redirect_url`: string - `payment.status`: string - `payment.va_bank`: string - `payment.va_number`: string - `track_token`: string - `track_url`: string ## POST /orders/lookup/otp/send Send lookup code Request: - `contact`: string Response 200: - `channel`: string - `expires_in_seconds`: integer - `masked`: string - `sent`: boolean ## POST /orders/lookup/otp/verify List buyer's orders Request: - `code`: string - `contact`: string Response 200: - `orders[].awb`: string - `orders[].buyer_claimed_at`: string (date-time) - `orders[].city`: string - `orders[].contact_masked`: string - `orders[].courier_code`: string - `orders[].customer_name`: string - `orders[].discount_total`: integer - `orders[].expires_at`: string (date-time) - `orders[].id`: string - `orders[].lines[].image`: string - `orders[].lines[].image_srcset`: string - `orders[].lines[].line_total`: integer - `orders[].lines[].name`: string - `orders[].lines[].preorder`: boolean - `orders[].lines[].qty`: integer - `orders[].lines[].sku`: string - `orders[].lines[].undiscounted_unit_price`: integer - `orders[].lines[].unit_price`: integer - `orders[].lines[].unlimited_stock`: boolean - `orders[].lines[].variant_label`: string - `orders[].number`: string - `orders[].payment_fee`: integer - `orders[].payment_method`: string - `orders[].placed_at`: string (date-time) - `orders[].preorder_lead_days`: integer - `orders[].preorder_ship_estimate`: string (date-time) - `orders[].promotion_name`: string - `orders[].shipping_cost`: integer - `orders[].shipping_discount`: integer - `orders[].shipping_promotion_name`: string - `orders[].status`: string - `orders[].status_label`: string - `orders[].subtotal`: integer - `orders[].timeline[].done`: boolean - `orders[].timeline[].key`: string - `orders[].timeline[].label`: string - `orders[].total`: integer - `orders[].unique_code`: integer - `orders[].voucher_code`: string ## GET /orders/track/{token} Track order Response 200: - `order.awb`: string - `order.buyer_claimed_at`: string (date-time) - `order.city`: string - `order.contact_masked`: string - `order.courier_code`: string - `order.customer_name`: string - `order.discount_total`: integer - `order.expires_at`: string (date-time) - `order.id`: string - `order.lines[].image`: string - `order.lines[].image_srcset`: string - `order.lines[].line_total`: integer - `order.lines[].name`: string - `order.lines[].preorder`: boolean - `order.lines[].qty`: integer - `order.lines[].sku`: string - `order.lines[].undiscounted_unit_price`: integer - `order.lines[].unit_price`: integer - `order.lines[].unlimited_stock`: boolean - `order.lines[].variant_label`: string - `order.number`: string - `order.payment_fee`: integer - `order.payment_method`: string - `order.placed_at`: string (date-time) - `order.preorder_lead_days`: integer - `order.preorder_ship_estimate`: string (date-time) - `order.promotion_name`: string - `order.shipping_cost`: integer - `order.shipping_discount`: integer - `order.shipping_promotion_name`: string - `order.status`: string - `order.status_label`: string - `order.subtotal`: integer - `order.timeline[].done`: boolean - `order.timeline[].key`: string - `order.timeline[].label`: string - `order.total`: integer - `order.unique_code`: integer - `order.voucher_code`: string - `payment.amount`: integer - `payment.expires_at`: string (date-time) - `payment.kind`: string - `payment.manual_account_holder`: string - `payment.manual_account_number`: string - `payment.manual_bank`: string - `payment.manual_qr_image_url`: string - `payment.manual_whatsapp_phone`: string - `payment.method`: string - `payment.qr_image_url`: string - `payment.qr_payload`: string - `payment.redirect_url`: string - `payment.status`: string - `payment.va_bank`: string - `payment.va_number`: string ## POST /orders/track/{token}/payment Re-open payment Response 200: - `payment.amount`: integer - `payment.expires_at`: string (date-time) - `payment.kind`: string - `payment.manual_account_holder`: string - `payment.manual_account_number`: string - `payment.manual_bank`: string - `payment.manual_qr_image_url`: string - `payment.manual_whatsapp_phone`: string - `payment.method`: string - `payment.qr_image_url`: string - `payment.qr_payload`: string - `payment.redirect_url`: string - `payment.status`: string - `payment.va_bank`: string - `payment.va_number`: string ## POST /orders/track/{token}/payment/claim Claim manual payment Response 200: - `order.awb`: string - `order.buyer_claimed_at`: string (date-time) - `order.city`: string - `order.contact_masked`: string - `order.courier_code`: string - `order.customer_name`: string - `order.discount_total`: integer - `order.expires_at`: string (date-time) - `order.id`: string - `order.lines[].image`: string - `order.lines[].image_srcset`: string - `order.lines[].line_total`: integer - `order.lines[].name`: string - `order.lines[].preorder`: boolean - `order.lines[].qty`: integer - `order.lines[].sku`: string - `order.lines[].undiscounted_unit_price`: integer - `order.lines[].unit_price`: integer - `order.lines[].unlimited_stock`: boolean - `order.lines[].variant_label`: string - `order.number`: string - `order.payment_fee`: integer - `order.payment_method`: string - `order.placed_at`: string (date-time) - `order.preorder_lead_days`: integer - `order.preorder_ship_estimate`: string (date-time) - `order.promotion_name`: string - `order.shipping_cost`: integer - `order.shipping_discount`: integer - `order.shipping_promotion_name`: string - `order.status`: string - `order.status_label`: string - `order.subtotal`: integer - `order.timeline[].done`: boolean - `order.timeline[].key`: string - `order.timeline[].label`: string - `order.total`: integer - `order.unique_code`: integer - `order.voucher_code`: string - `payment.amount`: integer - `payment.expires_at`: string (date-time) - `payment.kind`: string - `payment.manual_account_holder`: string - `payment.manual_account_number`: string - `payment.manual_bank`: string - `payment.manual_qr_image_url`: string - `payment.manual_whatsapp_phone`: string - `payment.method`: string - `payment.qr_image_url`: string - `payment.qr_payload`: string - `payment.redirect_url`: string - `payment.status`: string - `payment.va_bank`: string - `payment.va_number`: string ## GET /products List products Response 200: - `limit`: integer - `offset`: integer - `products[].availability`: string - `products[].badge`: string - `products[].category_key`: string - `products[].category_label`: string - `products[].compare_at_price`: integer - `products[].has_variants`: boolean - `products[].id`: string - `products[].image`: string - `products[].image_height`: integer - `products[].image_srcset`: string - `products[].image_width`: integer - `products[].name`: string - `products[].on_sale`: boolean - `products[].preorder`: boolean - `products[].preorder_lead_days`: integer - `products[].price`: integer - `products[].price_max`: integer - `products[].sell_from`: string (date-time) - `products[].sell_until`: string (date-time) - `products[].slug`: string - `products[].sold_out`: boolean - `products[].stock`: integer - `products[].unlimited_stock`: boolean ## GET /products/{slug} Product by slug Response 200: - `availability`: string - `badge`: string - `care_copy`: string - `category_key`: string - `category_label`: string - `compare_at_price`: integer - `description`: string - `has_variants`: boolean - `id`: string - `image`: string - `image_height`: integer - `image_srcset`: string - `image_width`: integer - `images[].alt`: string - `images[].height`: integer - `images[].id`: string - `images[].srcset`: string - `images[].url`: string - `images[].width`: integer - `material_spec`: string - `name`: string - `on_sale`: boolean - `options[].name`: string - `options[].values[]`: string - `preorder`: boolean - `preorder_lead_days`: integer - `price`: integer - `price_max`: integer - `related[].availability`: string - `related[].badge`: string - `related[].category_key`: string - `related[].category_label`: string - `related[].compare_at_price`: integer - `related[].has_variants`: boolean - `related[].id`: string - `related[].image`: string - `related[].image_height`: integer - `related[].image_srcset`: string - `related[].image_width`: integer - `related[].name`: string - `related[].on_sale`: boolean - `related[].preorder`: boolean - `related[].preorder_lead_days`: integer - `related[].price`: integer - `related[].price_max`: integer - `related[].sell_from`: string (date-time) - `related[].sell_until`: string (date-time) - `related[].slug`: string - `related[].sold_out`: boolean - `related[].stock`: integer - `related[].unlimited_stock`: boolean - `sell_from`: string (date-time) - `sell_until`: string (date-time) - `slug`: string - `sold_out`: boolean - `spec_label`: string - `stock`: integer - `unlimited_stock`: boolean - `variants[].badge`: string - `variants[].compare_at_price`: integer - `variants[].id`: string - `variants[].image_id`: string - `variants[].on_sale`: boolean - `variants[].options.x`: string - `variants[].price`: integer - `variants[].sku`: string - `variants[].sold_out`: boolean - `variants[].stock`: integer - `variants[].weight_grams`: integer ## POST /shipping/destinations Search destinations Request: - `limit`: integer - `q`: string Response 200: - `results[].city`: string - `results[].district`: string - `results[].label`: string - `results[].province`: string - `results[].ref`: string - `results[].subdistrict`: string - `results[].zip_code`: string ## POST /shipping/destinations/resolve Resolve destination Request: - `ref`: string Response 200: - `destination.city`: string - `destination.district`: string - `destination.label`: string - `destination.province`: string - `destination.ref`: string - `destination.subdistrict`: string - `destination.zip_code`: string ## POST /shipping/rates Shipping quotes Request: - `destination_label`: string - `destination_ref`: string - `items[].product_id`: string - `items[].qty`: integer - `items[].variant_id`: string Response 200: - `destination_label`: string - `free_shipping`: boolean - `options[].cost`: integer - `options[].courier_code`: string - `options[].courier_name`: string - `options[].courier_service`: string - `options[].etd`: string - `options[].name`: string - `options[].quote_id`: string - `weight_grams`: integer ## GET /storefront/config Storefront config Response 200: - `brand.about_body`: string - `brand.about_image.alt`: string - `brand.about_image.srcset`: string - `brand.about_image.url`: string - `brand.about_title`: string - `brand.address_line1`: string - `brand.address_line2`: string - `brand.copyright`: string - `brand.email`: string - `brand.hero_eyebrow`: string - `brand.hero_images[].alt`: string - `brand.hero_images[].srcset`: string - `brand.hero_images[].url`: string - `brand.hero_stats[].label`: string - `brand.hero_stats[].value`: string - `brand.logo_image.alt`: string - `brand.logo_image.srcset`: string - `brand.logo_image.url`: string - `brand.nav[].label`: string - `brand.nav[].route`: string - `brand.origin_city`: string - `brand.product_care_copy`: string - `brand.product_shipping_copy`: string - `brand.section_copy.cats_heading`: string - `brand.section_copy.grid_see_all`: string - `brand.section_copy.product_care_heading`: string - `brand.section_copy.product_details_heading`: string - `brand.section_copy.product_shipping_heading`: string - `brand.section_copy.story_cta`: string - `brand.section_copy.story_eyebrow`: string - `brand.social_image.alt`: string - `brand.social_image.srcset`: string - `brand.social_image.url`: string - `brand.tagline`: string - `brand.tagline_short`: string - `brand.template`: string - `brand.whatsapp_text`: string - `brand.whatsapp_url`: string - `brand.wordmark_accent`: string - `brand.wordmark_primary`: string - `commerce.couriers[].code`: string - `commerce.couriers[].cost`: integer - `commerce.couriers[].enabled`: boolean - `commerce.couriers[].eta`: string - `commerce.couriers[].name`: string - `commerce.couriers[].service`: string - `commerce.couriers[].services[]`: string - `commerce.free_shipping_disabled`: boolean - `commerce.free_shipping_min`: integer - `commerce.payment_methods[].code`: string - `commerce.payment_methods[].enabled`: boolean - `commerce.payment_methods[].fee_basis_pt`: integer - `commerce.payment_methods[].fee_flat`: integer - `commerce.payment_methods[].max_order_total`: integer - `commerce.payment_methods[].min_order_total`: integer - `commerce.payment_methods[].name`: string - `commerce.payment_methods[].sub`: string - `commerce.shipping.mode`: string - `contact.channel`: string - `payment.account_holder`: string - `payment.qris_merchant_name`: string - `payment.qris_nmid`: string - `tenant.name`: string - `tenant.slug`: string ## GET /storefront/home Home page Response 200: - `announcement.enabled`: boolean - `announcement.text`: string - `sections[].banner.bg`: string - `sections[].banner.cta`: string - `sections[].banner.text`: string - `sections[].campaign.badge`: string - `sections[].campaign.discount_type`: string - `sections[].campaign.discount_value`: integer - `sections[].campaign.ends_at`: string (date-time) - `sections[].campaign.products[].availability`: string - `sections[].campaign.products[].badge`: string - `sections[].campaign.products[].category_key`: string - `sections[].campaign.products[].category_label`: string - `sections[].campaign.products[].compare_at_price`: integer - `sections[].campaign.products[].has_variants`: boolean - `sections[].campaign.products[].id`: string - `sections[].campaign.products[].image`: string - `sections[].campaign.products[].image_height`: integer - `sections[].campaign.products[].image_srcset`: string - `sections[].campaign.products[].image_width`: integer - `sections[].campaign.products[].name`: string - `sections[].campaign.products[].on_sale`: boolean - `sections[].campaign.products[].preorder`: boolean - `sections[].campaign.products[].preorder_lead_days`: integer - `sections[].campaign.products[].price`: integer - `sections[].campaign.products[].price_max`: integer - `sections[].campaign.products[].sell_from`: string (date-time) - `sections[].campaign.products[].sell_until`: string (date-time) - `sections[].campaign.products[].slug`: string - `sections[].campaign.products[].sold_out`: boolean - `sections[].campaign.products[].stock`: integer - `sections[].campaign.products[].unlimited_stock`: boolean - `sections[].campaign.title`: string - `sections[].cats[].image`: string - `sections[].cats[].image_srcset`: string - `sections[].cats[].key`: string - `sections[].cats[].label`: string - `sections[].cats[].material_spec`: string - `sections[].cats[].spec_label`: string - `sections[].cats[].subtitle`: string - `sections[].grid.products[].availability`: string - `sections[].grid.products[].badge`: string - `sections[].grid.products[].category_key`: string - `sections[].grid.products[].category_label`: string - `sections[].grid.products[].compare_at_price`: integer - `sections[].grid.products[].has_variants`: boolean - `sections[].grid.products[].id`: string - `sections[].grid.products[].image`: string - `sections[].grid.products[].image_height`: integer - `sections[].grid.products[].image_srcset`: string - `sections[].grid.products[].image_width`: integer - `sections[].grid.products[].name`: string - `sections[].grid.products[].on_sale`: boolean - `sections[].grid.products[].preorder`: boolean - `sections[].grid.products[].preorder_lead_days`: integer - `sections[].grid.products[].price`: integer - `sections[].grid.products[].price_max`: integer - `sections[].grid.products[].sell_from`: string (date-time) - `sections[].grid.products[].sell_until`: string (date-time) - `sections[].grid.products[].slug`: string - `sections[].grid.products[].sold_out`: boolean - `sections[].grid.products[].stock`: integer - `sections[].grid.products[].unlimited_stock`: boolean - `sections[].grid.title`: string - `sections[].hero.cta`: string - `sections[].hero.em`: string - `sections[].hero.sub`: string - `sections[].hero.title`: string - `sections[].id`: string - `sections[].story.text`: string - `sections[].story.title`: string - `sections[].type`: string - `server_time`: string (date-time) ## GET /storefront/page/{key} Page by key Response 200: - `announcement.enabled`: boolean - `announcement.text`: string - `sections[].banner.bg`: string - `sections[].banner.cta`: string - `sections[].banner.text`: string - `sections[].campaign.badge`: string - `sections[].campaign.discount_type`: string - `sections[].campaign.discount_value`: integer - `sections[].campaign.ends_at`: string (date-time) - `sections[].campaign.products[].availability`: string - `sections[].campaign.products[].badge`: string - `sections[].campaign.products[].category_key`: string - `sections[].campaign.products[].category_label`: string - `sections[].campaign.products[].compare_at_price`: integer - `sections[].campaign.products[].has_variants`: boolean - `sections[].campaign.products[].id`: string - `sections[].campaign.products[].image`: string - `sections[].campaign.products[].image_height`: integer - `sections[].campaign.products[].image_srcset`: string - `sections[].campaign.products[].image_width`: integer - `sections[].campaign.products[].name`: string - `sections[].campaign.products[].on_sale`: boolean - `sections[].campaign.products[].preorder`: boolean - `sections[].campaign.products[].preorder_lead_days`: integer - `sections[].campaign.products[].price`: integer - `sections[].campaign.products[].price_max`: integer - `sections[].campaign.products[].sell_from`: string (date-time) - `sections[].campaign.products[].sell_until`: string (date-time) - `sections[].campaign.products[].slug`: string - `sections[].campaign.products[].sold_out`: boolean - `sections[].campaign.products[].stock`: integer - `sections[].campaign.products[].unlimited_stock`: boolean - `sections[].campaign.title`: string - `sections[].cats[].image`: string - `sections[].cats[].image_srcset`: string - `sections[].cats[].key`: string - `sections[].cats[].label`: string - `sections[].cats[].material_spec`: string - `sections[].cats[].spec_label`: string - `sections[].cats[].subtitle`: string - `sections[].grid.products[].availability`: string - `sections[].grid.products[].badge`: string - `sections[].grid.products[].category_key`: string - `sections[].grid.products[].category_label`: string - `sections[].grid.products[].compare_at_price`: integer - `sections[].grid.products[].has_variants`: boolean - `sections[].grid.products[].id`: string - `sections[].grid.products[].image`: string - `sections[].grid.products[].image_height`: integer - `sections[].grid.products[].image_srcset`: string - `sections[].grid.products[].image_width`: integer - `sections[].grid.products[].name`: string - `sections[].grid.products[].on_sale`: boolean - `sections[].grid.products[].preorder`: boolean - `sections[].grid.products[].preorder_lead_days`: integer - `sections[].grid.products[].price`: integer - `sections[].grid.products[].price_max`: integer - `sections[].grid.products[].sell_from`: string (date-time) - `sections[].grid.products[].sell_until`: string (date-time) - `sections[].grid.products[].slug`: string - `sections[].grid.products[].sold_out`: boolean - `sections[].grid.products[].stock`: integer - `sections[].grid.products[].unlimited_stock`: boolean - `sections[].grid.title`: string - `sections[].hero.cta`: string - `sections[].hero.em`: string - `sections[].hero.sub`: string - `sections[].hero.title`: string - `sections[].id`: string - `sections[].story.text`: string - `sections[].story.title`: string - `sections[].type`: string - `server_time`: string (date-time) ## Schema: Error - `error.code`: string - `error.details.x`: string - `error.message`: string ## Schema: ErrorDestinationUnserved - `error.code`: string - `error.details.alternatives[].city`: string - `error.details.alternatives[].district`: string - `error.details.alternatives[].label`: string - `error.details.alternatives[].province`: string - `error.details.alternatives[].ref`: string - `error.details.alternatives[].subdistrict`: string - `error.details.alternatives[].zip_code`: string - `error.message`: string