{"openapi":"3.1.0","info":{"title":"Littlestall Headless API","summary":"Read a store's catalog: everything a storefront renders.","description":"The API a storefront reads. The catalog is public — there is no key to send for it — and each request names the store it reads in its path.\n\nA shopper signs in with a code mailed to them, and what comes back is a bearer token for that store. Anything of the shopper's own is read with it in an `Authorization` header.\n\nPrices are JSON strings (`\"24.99\"`), not numbers, so a currency amount never round-trips through a float — the store says what they are denominated in via its `currency_code`.\n\nThe official TypeScript client is [`@littlestall/sdk`](https://www.npmjs.com/package/@littlestall/sdk).","contact":{"name":"Chamoda Pandithage","email":"chamoda@xaventra.com"},"version":"1.0.0"},"servers":[{"url":"/headless/v1"},{"url":"https://api.littlestall.com/headless/v1","description":"Production"},{"url":"http://localhost:8000/headless/v1","description":"Local development"}],"paths":{"/health":{"get":{"tags":["meta"],"summary":"Health check","description":"Whether the API is serving. Answers 503 when it is not.","operationId":"health","responses":{"200":{"description":"The service status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthCheckResponse"}}}}}}},"/countries":{"get":{"tags":["countries"],"summary":"List countries","description":"The countries an address may name, in alphabetical order, each with the ISO 3166-1 alpha-2 code an order's address carries and the calling code for its phone numbers.\n\nOnly the countries the platform ships to are listed, so a checkout builds its country picker from this rather than from a list of its own that would drift.","operationId":"country_list","responses":{"200":{"description":"Every country an address may name.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CountryResponse"},"type":"array","title":"Response Country List"}}}}}}},"/stores/{store_slug}":{"get":{"tags":["stores"],"summary":"Get a store","description":"The store behind a storefront: its name, its slug, and the ISO 4217 currency every price in the catalog is denominated in. A store the merchant has switched off answers 404, so nothing of it is served while it is closed.","operationId":"store_get","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreResponse"}}}},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/products":{"get":{"tags":["products"],"summary":"List products","description":"The store's listed products, newest first, each one whole: its media in display order, the options a shopper chooses from, and every variant those options make, priced. Only products the merchant has made active are listed — an unlisted one is reachable by slug alone, and a draft is never served.","operationId":"product_list","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size limit","default":50,"title":"Limit"},"description":"Page size limit"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Page offset","default":0,"title":"Offset"},"description":"Page offset"}],"responses":{"200":{"description":"A page of products.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOffsetPage_ProductResponse_"}}}},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/products/{product_slug}":{"get":{"tags":["products"],"summary":"Get a product","description":"One product by the slug the storefront puts in its own URLs. Resolves for both an active product and an unlisted one, so a link the merchant hands out works while nothing leads to it on its own; anything else answers 404.","operationId":"product_get","parameters":[{"name":"product_slug","in":"path","required":true,"schema":{"type":"string","title":"Product Slug"}},{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The product.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductResponse"}}}},"404":{"description":"No such product in this store."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/collections":{"get":{"tags":["collections"],"summary":"List collections","description":"The store's active collections, newest first — the groupings a storefront builds its navigation from. A collection the merchant has switched off is not listed. What each one holds is answered by the lookup below rather than here: a shop with a dozen collections would otherwise answer a nav menu with every product in every one of them.","operationId":"collection_list","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size limit","default":50,"title":"Limit"},"description":"Page size limit"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Page offset","default":0,"title":"Offset"},"description":"Page offset"}],"responses":{"200":{"description":"A page of collections.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOffsetPage_CollectionResponse_"}}}},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/collections/{collection_slug}":{"get":{"tags":["collections"],"summary":"Get a collection","description":"One collection by the slug the storefront puts in its own URLs, with the products it holds in the arrangement the merchant made. Each product arrives whole — media, options and priced variants — so a collection page renders from this one response.\n\nMembership is not publication: a product that is a draft, or that the merchant no longer sells through the headless channel, is left out rather than served for the client to hide.","operationId":"collection_get","parameters":[{"name":"collection_slug","in":"path","required":true,"schema":{"type":"string","title":"Collection Slug"}},{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The collection and its products.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionProductsResponse"}}}},"404":{"description":"No such collection in this store."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/menus":{"get":{"tags":["menus"],"summary":"List menus","description":"Every menu the store has, each with its items nested as the merchant arranged them. A storefront reads this once to build its header, its footer and anything else it hangs off a menu.\n\nEach item carries a resolved `url`: the address the item points at, whether the merchant typed it or named a product or a collection. An item naming something since deleted is left out rather than served as a dead link.","operationId":"menu_list","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The store's menus.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MenuResponse"},"title":"Response Menu List"}}}},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/menus/{menu_slug}":{"get":{"tags":["menus"],"summary":"Get a menu","description":"One menu by its slug — `main-menu`, `footer-menu`, or whatever the merchant named theirs — with its items nested as they were arranged.","operationId":"menu_get","parameters":[{"name":"menu_slug","in":"path","required":true,"schema":{"type":"string","title":"Menu Slug"}},{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The menu and its items.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuResponse"}}}},"404":{"description":"No such menu in this store."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/auth/request-otp":{"post":{"tags":["auth"],"summary":"Request a sign-in code","description":"Mails a six-character code to the address given. The address is all this takes — the same call signs a shopper in and signs them up, so there is nothing to say which of the two it is.\n\nThe answer is the same whether or not that address has an account, so nothing here can be used to ask whether a given person shops at this store. The code is never in the response — the point of it is that it arrives in the inbox.\n\nCodes are limited to fifteen an hour per address.","operationId":"customer_request_otp","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"200":{"description":"The code is on its way.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"404":{"description":"No such store, or the merchant has closed it."},"429":{"description":"Too many codes asked for. Try again later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/auth/verify-otp":{"post":{"tags":["auth"],"summary":"Verify a sign-in code","description":"Redeems a code and opens a session. A code works once, expires five minutes after it was asked for, and is spent after three wrong tries.\n\nIf the address has no account here yet, one is created from it — so the same call signs a shopper in and signs them up. A name and a phone number are not asked for until checkout.\n\nThe session comes back as a bearer token, good for 30 days. Send it as `Authorization: Bearer <token>` on anything that reads or writes the shopper's own data. It is only a session at this store.","operationId":"customer_verify_otp","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPVerifyRequest"}}}},"responses":{"200":{"description":"The session, and the shopper it belongs to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerSessionResponse"}}}},"400":{"description":"The code has expired, has been used, or has been tried too many times."},"401":{"description":"That is not the code that was sent."},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/customers/self":{"get":{"tags":["customers"],"summary":"Get the signed-in customer","description":"The shopper the session belongs to, with the address book they may ship to. This is what a storefront calls on load to find out whether the token it is holding is still good — an expired or withdrawn one answers 401 here rather than failing later on something that mattered.","operationId":"customer_get_self","security":[{"CustomerSession":[]}],"parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The signed-in customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerResponse"}}}},"401":{"description":"No session, or one that is no longer good."},"403":{"description":"The merchant has disabled this account."},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/orders":{"get":{"tags":["orders"],"summary":"List the shopper's orders","description":"The signed-in shopper's own orders, newest first, each with its lines and the product behind every line — enough to draw an order history and link each row back to the thing that was bought.\n\nEvery line keeps the title, SKU and price it had when the order was placed. The product nested beside it is the live one, which the merchant may have renamed or repriced since; it is there to link to and to show a picture, never to say what was charged.\n\nOrders placed without signing in are here too, as long as they were placed with this account's email address: checking out files an order under the address it was given, so signing in afterwards collects what that address has already bought.","operationId":"order_list","security":[{"CustomerSession":[]}],"parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size limit","default":50,"title":"Limit"},"description":"Page size limit"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Page offset","default":0,"title":"Offset"},"description":"Page offset"}],"responses":{"200":{"description":"A page of the shopper's orders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOffsetPage_OrderResponse_"}}}},"401":{"description":"No session, or one that is no longer good."},"403":{"description":"The merchant has disabled this account."},"404":{"description":"No such store, or the merchant has closed it."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["orders"],"summary":"Place an order","description":"Places an order. A session is optional: a shopper who is signed in has the order attached to their account, and one who is not still buys. Nothing about a checkout requires signing up.\n\nEither way the order is filed under a customer of this store. The email given is looked up among them: an address the store already knows files the order under that account, and one it does not gets a customer record made from the checkout — the name and number on the shipping address, and the address itself as where to write. That record is only ever added to, never overwritten, so what a shopper has set on their own account outranks a delivery form filled in for somebody else. Asking for a sign-in code with that address later is what opens the account and shows the orders it has already made.\n\nThe request names what is being bought, where it is going and how it is being paid for, and nothing about money: prices, the subtotal and the total are read off the catalog here, so what is charged is what the merchant set.\n\nStock is checked and taken in the same locked transaction, so two shoppers racing for the last one cannot both have it. The order keeps its own copy of the addresses and of every line's title, SKU and price, which is what makes it a record rather than a view of a catalog that will move on without it.","operationId":"order_create","security":[{"CustomerSession":[]}],"parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCreate"}}}},"responses":{"201":{"description":"The order that was placed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"400":{"description":"A payment method this store does not offer, or not enough stock to fill the basket."},"401":{"description":"A session was sent, and it is no longer good."},"403":{"description":"The merchant has disabled this account."},"404":{"description":"Something in the basket is no longer for sale."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/orders/{order_id}/invoice":{"post":{"tags":["orders"],"summary":"Send an order's invoice","description":"Mails the invoice for one of the shopper's own orders to the address the order was placed with, as plain text. The same document the shopper is sent when payment lands — this is how they ask for another copy of it.\n\nAnswers before the mail is sent, so a slow mail server does not keep the caller waiting.","operationId":"order_invoice_send","security":[{"CustomerSession":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Order Id"}},{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"The invoice is on its way.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"401":{"description":"No session, or one that is no longer good."},"403":{"description":"The merchant has disabled this account."},"404":{"description":"No such order of the shopper's."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/store-payment-methods":{"get":{"tags":["payment-methods"],"summary":"Store Payment Method List","description":"List payment methods.\n\nThe ways this store can be paid, in the order they should be offered. Only\nthe ones the merchant has switched on are listed, and each carries the\ninstruction the shopper needs to see when they pick it — where to deposit,\nor that the courier will collect cash.\n\nA `card` method carries the `payment_gateway` behind it, so a checkout can\noffer \"Card\" once and let the shopper pick between the providers under it\nrather than listing each gateway as though it were a payment method of its\nown. Everything else has a null gateway.\n\nA checkout reads this to build its payment step rather than hard-coding a\nlist, so a merchant turning a method on or off changes the storefront\nwithout a deploy.","operationId":"store_payment_method_list","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StorePaymentMethodResponse"},"title":"Response Store Payment Method List"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/store-payment-gateways":{"get":{"tags":["payment-gateways"],"summary":"Store Payment Gateway List","description":"List payment gateways.\n\nThe card providers this shop can be paid through, in the order they were\nconnected. Only the ones the merchant has switched on: a gateway that is\noff is a payment with nothing behind it.\n\nThe same providers are already named inside the `card` entries of\n`store-payment-methods`, which is all a checkout needs to draw its payment\nstep. This is for a storefront that works the other way round — one that\nputs the card providers up front, or loads a provider's own client SDK and\nneeds to know which one and whether it is running against a sandbox.\n\nEach entry carries `store_payment_method_id`, which is what an order names\nas its `store_payment_method_id`, so a checkout built from this list never\nhas to read the payment method list as well.","operationId":"store_payment_gateway_list","parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StorePaymentGatewayResponse"},"title":"Response Store Payment Gateway List"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/checkouts":{"post":{"tags":["checkouts"],"summary":"Checkout Create","description":"Start a card payment.\n\nTakes the same order a `POST /orders` would, plus the storefront page to\nreturn to, and answers with the provider page to send the browser to. The\norder does not exist yet and will not until the money is confirmed.\n\nWhat the shopper owes is priced here from the catalog. A total is never\nread off the request — a client that could name its own price would be a\nclient that could set it.\n\nSend the browser to `redirect_url`. The shopper comes back to the\n`return_url` given here with `?checkout=<id>&status=<paid|failed>`, and\nthat status is the storefront's cue to draw a page, not its evidence: read\n`GET /stores/{store_slug}/checkouts/{checkout_id}` for the shop's own\nanswer.","operationId":"checkout_create","security":[{"CustomerSession":[]}],"parameters":[{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stores/{store_slug}/checkouts/{checkout_id}":{"get":{"tags":["checkouts"],"summary":"Checkout Get","description":"How a card payment ended.\n\nWhat the page the shopper lands on should draw from. The `status` in the\nreturn URL is the storefront's first guess; this is the shop's answer, and\nit is the one to show.\n\nSend the `secret` from `POST /checkouts` as `X-Checkout-Secret`. The id\nalone is not enough: it travels in a URL and so ends up in browser history,\nin a `Referer` header and in whatever analytics the storefront runs, and a\ncheckout readable by id would be readable by all of them. A wrong or\nmissing secret answers 404 rather than 403 — a 403 would confirm the\ncheckout exists.\n\n`order_number` is set once the payment landed and the order was written. A\nfailed payment has none, and `failure_reason` says why in words a shopper\ncan read.","operationId":"checkout_get","parameters":[{"name":"checkout_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Checkout Id"}},{"name":"store_slug","in":"path","required":true,"schema":{"type":"string","title":"Store Slug"}},{"name":"x-checkout-secret","in":"header","required":true,"schema":{"type":"string","title":"X-Checkout-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AddressCreate":{"properties":{"first_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Last Name"},"address1":{"type":"string","maxLength":255,"minLength":1,"title":"Address1"},"address2":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Address2"},"city":{"type":"string","maxLength":100,"minLength":1,"title":"City"},"province":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Province"},"postal_code":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Postal Code"},"country_code":{"type":"string","maxLength":2,"minLength":2,"title":"Country Code"},"phone_number":{"anyOf":[{"type":"string","maxLength":32,"minLength":7},{"type":"null"}],"title":"Phone Number","description":"A phone number in full international form, with its country code. Any country: it is checked against that country's own numbering plan. Punctuation is taken out and it is stored as E.164.","examples":["+447400123456","+919876543210","+94771234567"]}},"type":"object","required":["address1","city","country_code"],"title":"AddressCreate","description":"Where an order is going, as the shopper typed it.\n\nTaken as an address rather than as the id of a saved one: somebody checking\nout for the first time has no address book, and requiring one would mean\nsigning up before buying."},"CheckoutCreate":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"},"items":{"items":{"$ref":"#/components/schemas/OrderItemCreate"},"type":"array","minItems":1,"title":"Items"},"shipping_address":{"$ref":"#/components/schemas/AddressCreate"},"billing_address":{"anyOf":[{"$ref":"#/components/schemas/AddressCreate"},{"type":"null"}]},"store_payment_method_id":{"type":"string","format":"uuid4","title":"Store Payment Method Id"},"channel":{"type":"string","title":"Channel","default":"headless"},"return_url":{"type":"string","maxLength":2000,"minLength":8,"title":"Return Url"}},"type":"object","required":["email","items","shipping_address","store_payment_method_id","return_url"],"title":"CheckoutCreate","description":"An order a shopper is about to pay for by card.\n\nEverything an order needs, plus where to send them back to once the\nprovider is done with them. Nothing is written to `orders` from this: the\nbasket is held until the money is confirmed, and only then does it become\nan order. A payment that fails leaves no order behind at all."},"CheckoutResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"status":{"type":"string","title":"Status"},"redirect_url":{"type":"string","title":"Redirect Url"},"secret":{"type":"string","title":"Secret"}},"type":"object","required":["id","status","redirect_url","secret"],"title":"CheckoutResponse","description":"A card payment that has been opened, and where to send the shopper."},"CheckoutStatusResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"status":{"type":"string","title":"Status"},"order_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order Number"},"failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Reason"}},"type":"object","required":["id","status","order_number","failure_reason"],"title":"CheckoutStatusResponse","description":"How a card payment ended, for the page the shopper comes back to.\n\nRead after the redirect rather than trusted from it: the `status` in the\nURL is only what the storefront can draw first, and this is the shop's own\nanswer."},"CollectionProductItemResponse":{"properties":{"position":{"type":"integer","title":"Position"},"product":{"$ref":"#/components/schemas/ProductResponse"}},"type":"object","required":["position","product"],"title":"CollectionProductItemResponse"},"CollectionProductsResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"title":{"type":"string","title":"Title"},"slug":{"type":"string","title":"Slug"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"media":{"anyOf":[{"$ref":"#/components/schemas/MediaResponse"},{"type":"null"}]},"collection_products":{"items":{"$ref":"#/components/schemas/CollectionProductItemResponse"},"type":"array","title":"Collection Products"}},"type":"object","required":["id","created_at","updated_at","title","slug","description","media","collection_products"],"title":"CollectionProductsResponse","description":"One collection with what it holds, in the arrangement the merchant made.\n\nEach product arrives whole, the same shape the product endpoints answer\nwith, so a collection page renders from this one response."},"CollectionResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"title":{"type":"string","title":"Title"},"slug":{"type":"string","title":"Slug"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"media":{"anyOf":[{"$ref":"#/components/schemas/MediaResponse"},{"type":"null"}]}},"type":"object","required":["id","created_at","updated_at","title","slug","description","media"],"title":"CollectionResponse","description":"A collection as the storefront's own listing shows it.\n\nWithout its products: a shop with a dozen collections would otherwise\nanswer a nav menu with every product in every one of them. The products\ncome from the lookup by slug below, which is the collection's own page."},"CountryResponse":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"dial_code":{"type":"string","title":"Dial Code"}},"type":"object","required":["code","name","dial_code"],"title":"CountryResponse","description":"One country a shopper may give an address in.\n\nKeyed by its ISO 3166-1 alpha-2 `code`, which is what an order's address\ncarries. `dial_code` is the ITU calling code without the leading `+`, for a\ncheckout that offers to prefix a phone number with it."},"CustomerAddressResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"address1":{"type":"string","title":"Address1"},"address2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address2"},"city":{"type":"string","title":"City"},"province":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Province"},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code"},"country_code":{"type":"string","title":"Country Code"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number"},"is_default":{"type":"boolean","title":"Is Default"}},"type":"object","required":["id","first_name","last_name","address1","address2","city","province","postal_code","country_code","phone_number","is_default"],"title":"CustomerAddressResponse","description":"One place a shopper has told the shop about.\n\nThe country is its ISO code alone — the storefront prints what the shopper\ntyped, and the registry row behind that code is the console's business."},"CustomerResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"},"customer_addresses":{"items":{"$ref":"#/components/schemas/CustomerAddressResponse"},"type":"array","title":"Customer Addresses"}},"type":"object","required":["id","created_at","email","phone_number","first_name","last_name","last_login_at","customer_addresses"],"title":"CustomerResponse","description":"The signed-in shopper, with the address book they may ship to.\n\nThe book comes with them rather than from a call of its own: it is short,\nit is theirs, and an account page that had to ask for it separately would\nrender twice."},"CustomerSessionResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","const":"bearer","title":"Token Type","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In"},"customer":{"$ref":"#/components/schemas/CustomerResponse"}},"type":"object","required":["access_token","expires_in","customer"],"title":"CustomerSessionResponse","description":"A signed-in session: the token to send back, and who it belongs to.\n\nThe token is a bearer token — it goes in an `Authorization` header, and\nanything holding it is the customer as far as this API is concerned. It\ncarries no session on the server, so it stays valid for `expires_in`\nseconds whatever happens here in the meantime."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthCheckResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"HealthCheckResponse"},"LimitOffsetPage_CollectionResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CollectionResponse"},"type":"array","title":"Items"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"limit":{"type":"integer","minimum":1.0,"title":"Limit"},"offset":{"type":"integer","minimum":0.0,"title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"LimitOffsetPage[CollectionResponse]"},"LimitOffsetPage_OrderResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OrderResponse"},"type":"array","title":"Items"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"limit":{"type":"integer","minimum":1.0,"title":"Limit"},"offset":{"type":"integer","minimum":0.0,"title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"LimitOffsetPage[OrderResponse]"},"LimitOffsetPage_ProductResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ProductResponse"},"type":"array","title":"Items"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"limit":{"type":"integer","minimum":1.0,"title":"Limit"},"offset":{"type":"integer","minimum":0.0,"title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"LimitOffsetPage[ProductResponse]"},"MediaResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"type":{"type":"string","enum":["image","video"],"title":"Type"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alt"},"url":{"type":"string","title":"Url","readOnly":true}},"type":"object","required":["id","type","filename","content_type","alt","url"],"title":"MediaResponse"},"MenuItemResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"label":{"type":"string","title":"Label"},"position":{"type":"integer","title":"Position"},"link_type":{"type":"string","enum":["url","product","collection"],"title":"Link Type"},"url":{"type":"string","title":"Url"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"children":{"items":{"$ref":"#/components/schemas/MenuItemResponse"},"type":"array","title":"Children"}},"type":"object","required":["id","label","position","link_type","url","slug","children"],"title":"MenuItemResponse"},"MenuResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"title":{"type":"string","title":"Title"},"slug":{"type":"string","title":"Slug"},"menu_items":{"items":{"$ref":"#/components/schemas/MenuItemResponse"},"type":"array","title":"Menu Items"}},"type":"object","required":["id","title","slug","menu_items"],"title":"MenuResponse"},"MessageResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"MessageResponse"},"OTPRequest":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"}},"type":"object","required":["email"],"title":"OTPRequest","description":"Ask for a sign-in code.\n\nThe address is the whole of it. The same request signs a shopper in and\nsigns them up, so there is nothing here to say which of the two it is."},"OTPVerifyRequest":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"},"otp_code":{"type":"string","maxLength":6,"minLength":6,"pattern":"^[23456789ABCDEFGHJKLMNPQRSTUVWXYZ]+$","title":"Otp Code","examples":["BD6L37"]}},"type":"object","required":["email","otp_code"],"title":"OTPVerifyRequest","description":"Redeem a sign-in code."},"OrderCreate":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"},"items":{"items":{"$ref":"#/components/schemas/OrderItemCreate"},"type":"array","minItems":1,"title":"Items"},"shipping_address":{"$ref":"#/components/schemas/AddressCreate"},"billing_address":{"anyOf":[{"$ref":"#/components/schemas/AddressCreate"},{"type":"null"}]},"store_payment_method_id":{"type":"string","format":"uuid4","title":"Store Payment Method Id"},"channel":{"type":"string","title":"Channel","default":"headless"}},"type":"object","required":["email","items","shipping_address","store_payment_method_id"],"title":"OrderCreate","description":"An order, as the storefront asks for it.\n\nThe cart lives in the browser, so this is the first the server hears of it.\nWhat the shopper owes is never part of it: prices, the subtotal and the\ntotal are resolved here from the catalog.\n\nNo session is required, and `email` is asked for either way. A shopper who\nis signed in has the order attached to their account; one who is not has it\nattached to whichever account that address already names at this store, or\nto one made from this checkout if it names none. Either way the order is\nfiled under somebody, so signing in afterwards finds it."},"OrderItemCreate":{"properties":{"product_variant_id":{"type":"string","format":"uuid4","title":"Product Variant Id"},"quantity":{"type":"integer","exclusiveMinimum":0.0,"title":"Quantity"}},"type":"object","required":["product_variant_id","quantity"],"title":"OrderItemCreate","description":"One line a shopper is asking to buy.\n\nThe variant and how many of it, and nothing else. What it costs is read off\nthe variant when the order is placed — a client that could name its own\nprice would be a client that could set it."},"OrderItemProductResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"image":{"anyOf":[{"$ref":"#/components/schemas/MediaResponse"},{"type":"null"}],"description":"The product's first image, which is what an order list shows.","readOnly":true}},"type":"object","required":["slug","title","image"],"title":"OrderItemProductResponse","description":"The product an order line was for, as it stands today.\n\nEnough of it to draw the line and link back to it. Nothing here is what\nthe shopper was charged — the line keeps its own copy of the title and the\nprice from the moment the order was placed, and this is the live product,\nwhich the merchant may have renamed or repriced since."},"OrderItemResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"title":{"type":"string","title":"Title"},"variant_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variant Title"},"sku":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sku"},"price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Price"},"quantity":{"type":"integer","title":"Quantity"},"product_variant":{"anyOf":[{"$ref":"#/components/schemas/OrderItemVariantResponse"},{"type":"null"}]}},"type":"object","required":["id","title","variant_title","sku","price","quantity","product_variant"],"title":"OrderItemResponse","description":"One line of an order: what was bought, at what price, and how many.\n\nThe title, SKU and price are the snapshot taken when the order was placed.\nThey are what the shopper agreed to and never change."},"OrderItemVariantResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"product":{"$ref":"#/components/schemas/OrderItemProductResponse"}},"type":"object","required":["id","product"],"title":"OrderItemVariantResponse","description":"The variant an order line points at, as it stands today.\n\nNull when the merchant has since deleted it. The line survives either way —\nit is a record of what was bought, not a link to a catalog that will move\non without it."},"OrderResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"order_number":{"type":"integer","title":"Order Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"currency_code":{"type":"string","title":"Currency Code"},"subtotal":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Subtotal"},"total":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total"},"payment_status":{"type":"string","title":"Payment Status"},"fulfillment_status":{"type":"string","title":"Fulfillment Status"},"items":{"items":{"$ref":"#/components/schemas/OrderItemResponse"},"type":"array","title":"Items"}},"type":"object","required":["id","order_number","created_at","currency_code","subtotal","total","payment_status","fulfillment_status","items"],"title":"OrderResponse","description":"The order that was placed.\n\n`order_number` is what the shopper and the merchant both call it; the id is\nwhat the API is addressed by."},"PaymentGatewayResponse":{"properties":{"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"}},"type":"object","required":["name","slug"],"title":"PaymentGatewayResponse","description":"The provider behind a card payment, as the shopper is told it.\n\nIts name and nothing else: which gateway a merchant uses is something a\nshopper sees at the moment they hand over a card, so it is named — but\nnothing about how the shop is connected to it is on this surface."},"ProductMediaResponse":{"properties":{"position":{"type":"integer","title":"Position"},"media":{"$ref":"#/components/schemas/MediaResponse"}},"type":"object","required":["position","media"],"title":"ProductMediaResponse"},"ProductOptionResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"name":{"type":"string","title":"Name"},"position":{"type":"integer","title":"Position"},"product_option_values":{"items":{"$ref":"#/components/schemas/ProductOptionValueResponse"},"type":"array","title":"Product Option Values"}},"type":"object","required":["id","name","position","product_option_values"],"title":"ProductOptionResponse"},"ProductOptionValueResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"product_option_id":{"type":"string","format":"uuid4","title":"Product Option Id"},"value":{"type":"string","title":"Value"},"position":{"type":"integer","title":"Position"}},"type":"object","required":["id","product_option_id","value","position"],"title":"ProductOptionValueResponse"},"ProductResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"title":{"type":"string","title":"Title"},"slug":{"type":"string","title":"Slug"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","enum":["active","unlisted"],"title":"Status"},"product_media":{"items":{"$ref":"#/components/schemas/ProductMediaResponse"},"type":"array","title":"Product Media"},"product_options":{"items":{"$ref":"#/components/schemas/ProductOptionResponse"},"type":"array","title":"Product Options"},"product_variants":{"items":{"$ref":"#/components/schemas/ProductVariantResponse"},"type":"array","title":"Product Variants"}},"type":"object","required":["id","created_at","updated_at","title","slug","description","status","product_media","product_options","product_variants"],"title":"ProductResponse"},"ProductVariantOptionResponse":{"properties":{"product_option_value":{"$ref":"#/components/schemas/ProductOptionValueResponse"}},"type":"object","required":["product_option_value"],"title":"ProductVariantOptionResponse"},"ProductVariantResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"title":{"type":"string","title":"Title"},"sku":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sku"},"barcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Barcode"},"price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Price"},"compare_at_price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Compare At Price"},"track_inventory":{"type":"boolean","title":"Track Inventory"},"inventory_quantity":{"type":"integer","title":"Inventory Quantity"},"position":{"type":"integer","title":"Position"},"product_variant_options":{"items":{"$ref":"#/components/schemas/ProductVariantOptionResponse"},"type":"array","title":"Product Variant Options"},"is_available":{"type":"boolean","title":"Is Available","readOnly":true}},"type":"object","required":["id","title","sku","barcode","price","compare_at_price","track_inventory","inventory_quantity","position","product_variant_options","is_available"],"title":"ProductVariantResponse"},"StorePaymentGatewayResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"payment_gateway":{"$ref":"#/components/schemas/PaymentGatewayResponse"},"is_test_mode":{"type":"boolean","title":"Is Test Mode"},"store_payment_method_id":{"anyOf":[{"type":"string","format":"uuid4"},{"type":"null"}],"title":"Store Payment Method Id"}},"type":"object","required":["id","payment_gateway","is_test_mode","store_payment_method_id"],"title":"StorePaymentGatewayResponse","description":"A gateway this store takes card payments through.\n\nThe provider, whether it is running against its sandbox, and the id of the\npayment method to send back when a shopper picks it. Nothing of how the\nshop is connected: the credentials are encrypted at rest and never leave\nthe server, and the endpoint a charge is sent to is the platform's to\ndecide, not something a storefront is told."},"StorePaymentMethodResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"type":{"type":"string","title":"Type"},"name":{"type":"string","title":"Name"},"additional_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Additional Details"},"payment_instruction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Instruction"},"payment_gateway":{"anyOf":[{"$ref":"#/components/schemas/PaymentGatewayResponse"},{"type":"null"}]}},"type":"object","required":["id","type","name","additional_details","payment_instruction"],"title":"StorePaymentMethodResponse","description":"A way this store can be paid.\n\n`payment_instruction` is what the shopper needs to see once they pick this\none — where to deposit, or that the courier will collect cash — and\n`additional_details` is what helps them choose it in the first place."},"StoreResponse":{"properties":{"id":{"type":"string","format":"uuid4","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"currency_code":{"type":"string","title":"Currency Code"}},"type":"object","required":["id","name","slug","currency_code"],"title":"StoreResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"CustomerSession":{"type":"http","scheme":"bearer"}}},"tags":[{"name":"meta","description":"Health and service metadata"},{"name":"stores","description":"The store a storefront is built for, and how its prices are denominated"},{"name":"products","description":"The catalog: products with their media, options and priced variants"},{"name":"auth","description":"Signing a shopper in with a code mailed to their address"},{"name":"customers","description":"The signed-in shopper's own record"},{"name":"orders","description":"Placing an order, and what the shopper was charged"},{"name":"payment-methods","description":"The ways a shop can be paid, and what to tell the shopper about each"},{"name":"payment-gateways","description":"The card providers a shop has connected, for a checkout that offers them directly"}]}