From b0b09e44fdfc3a88b654fe5882e26eef2ad9442a Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 7 Sep 2026 12:45:13 +0100 Subject: [PATCH 1/3] spec: rename the collection fulfilment method to pickup (0076) Co-Authored-By: Claude Fable 5 --- TODO.md | 1 + ...-rename-collection-fulfilment-to-pickup.md | 190 ++++++++++++++++++ specs/README.md | 2 + 3 files changed, 193 insertions(+) create mode 100644 specs/0076-rename-collection-fulfilment-to-pickup.md diff --git a/TODO.md b/TODO.md index 80ec77d951..47d7edceeb 100644 --- a/TODO.md +++ b/TODO.md @@ -12,6 +12,7 @@ Items tagged _(judgement)_ are genuine line-calls worth revisiting. - Attribute field-type configuration schema — declarative config surface for field types in the panel settings (spec 0054) - Default professional customer notifications for the order lifecycle (spec 0036) _(judgement)_ - Bulk order operations — goal-oriented bulk actions on the orders table (spec 0026) +- Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0076) - Order print templates — Print dropdown of selectable PDF templates, ships an Advice Note (spec 0027) - Cart/order line grouping — grouping key on the `*_lines` tables _(judgement)_ - Cart totals caching in the database — additive performance optimisation diff --git a/specs/0076-rename-collection-fulfilment-to-pickup.md b/specs/0076-rename-collection-fulfilment-to-pickup.md new file mode 100644 index 0000000000..2beed0b53f --- /dev/null +++ b/specs/0076-rename-collection-fulfilment-to-pickup.md @@ -0,0 +1,190 @@ +# 0076 — Rename the collection fulfilment method to pickup + +- Status: proposed +- Author: Glenn Jacobs +- Created: 2026-09-07 +- TODO item: Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0076) + +## Problem + +Lunar calls the in-store handover fulfilment method "collection" and its +terminal state "collected". The rest of the e-commerce ecosystem settled on +"pickup": Shopify fulfilments move through `ready_for_pickup` → `picked_up` +and merchants configure "Local pickup"; WooCommerce ships a "Local pickup" +shipping method; Magento calls it "In-Store Pickup". Staff arriving from any +of those platforms — and integrations mapping Lunar's states onto external +systems — have to translate our vocabulary at every boundary. + +The word is also the most overloaded identifier in the codebase. Three +unrelated classes are named `Collection` (`Lunar\Core\Models\Collection`, the +catalogue tree; `Lunar\Core\Drivers\FulfilmentMethods\Collection`; and +`Lunar\Shipping\Drivers\ShippingMethods\Collection`), on top of +`Illuminate\Support\Collection` which nearly every file imports. Call sites +alias their way around it (`use ...\Collection as CollectionMethod` in +`FulfilmentMethodManifest` and the tests; `use Illuminate\Support\Collection +as SupportCollection` inside the driver itself), and grepping for the +fulfilment concept means wading through catalogue and Eloquent noise. + +The vocabulary appears in five places: + +1. **The fulfilment method key** — `Drivers/FulfilmentMethods/Collection.php`, + `KEY = 'collection'`, persisted in `fulfilments.method`. +2. **The state names** — `States/Fulfilment/ReadyForCollection.php` + (`ready-for-collection`) and `Collected.php` (`collected`), persisted in + `fulfilments.state`. +3. **The routing flag** — `ShippingOption::$collect`, stamped onto the order's + shipping line as `meta['collect']` by `CreateShippingLine` and read back by + the driver's `claim()`. +4. **UI strings keyed by the method key** — panel `orders.php` + (`handed_over_collection`, `fulfil_label_collection`), admin `order.php` + (`columns.handed_over.collection`, `actions.fulfil.labels.collection`), + core `fulfilment.php` / `states.php`, each across every shipped locale. +5. **The table-rate-shipping driver** — `Drivers/ShippingMethods/Collection`, + driver key `'collection'` persisted in `shipping_methods.driver`, offered + in the Filament driver select, sets `collect: true` on its options. + +## Proposal + +Rename the concept to "pickup" end to end — classes, persisted keys, the +routing flag, and the merchant-facing labels — matching Shopify's state +vocabulary exactly. + +| Current | Proposed | +| --- | --- | +| method key `collection` | `pickup` | +| `Lunar\Core\Drivers\FulfilmentMethods\Collection` | `Lunar\Core\Drivers\FulfilmentMethods\Pickup` | +| state `ready-for-collection` / `ReadyForCollection` | `ready-for-pickup` / `ReadyForPickup` | +| state `collected` / `Collected` | `picked-up` / `PickedUp` | +| `ShippingOption::$collect` / `meta['collect']` | `ShippingOption::$pickup` / `meta['pickup']` | +| `Lunar\Shipping\Drivers\ShippingMethods\Collection`, driver key `collection` | `...\Pickup`, driver key `pickup` | + +### Core + +- Rename the driver class to `Pickup`, `KEY = 'pickup'`, label from + `lunar::fulfilment.methods.pickup`. The `orderCollects()` helper becomes + `orderPicksUp()` and reads `meta['pickup']`. Registration in + `FulfilmentMethodManifest::registerCoreMethods()` drops its alias. +- Rename the states: `ReadyForPickup` (`$name = 'ready-for-pickup'`, label + "Ready for pickup") and `PickedUp` (`$name = 'picked-up'`, label + "Picked up"). Transition tables, `defaultState()`, and `fulfilledState()` + update mechanically. +- `ShippingOption` constructor property `collect` → `pickup`; + `CreateShippingLine` stamps `meta['pickup']`. +- `FulfilmentFactory` states `collection()` / `collected()` → + `pickup()` / `pickedUp()`. +- Docblock mentions in `Fulfilment`, `FulfilFulfilment`, + `TransitionFulfilment`, `FulfilsFulfilment`, `ResolveFulfilmentStatus`, and + `FulfilmentStateCategory` follow the new vocabulary. + +### Lang keys (every shipped locale) + +Key renames in four file families, values retranslated where the term changes: + +- core `states.php`: `fulfilment.ready-for-pickup`, `fulfilment.picked-up`. +- core `fulfilment.php`: `methods.pickup`. +- panel `orders.php`: `handed_over_pickup`, `fulfil_label_pickup` (the suffix + is the method key, looked up dynamically by `OrderShowController`). +- admin `order.php`: `columns.handed_over.pickup`, + `actions.fulfil.labels.pickup`. + +English values change to "Pickup", "Ready for pickup", "Picked up", +"Mark picked up", "Picked up at". Most other locales already translate the +concept as pickup (de `Abholung`, es `Recogida`, fr `Retrait`, ro `Ridicare`) +so only their keys move; each value is checked against the locale's existing +fulfilment terminology while touching the file (e.g. mn's literal +`Цуглуулга` in the table-rate-shipping driver options is a mistranslation — +it should match the `Очиж авах` used by mn `fulfilment.php`). + +### Table-rate shipping + +- Rename the shipping rate driver class to `Pickup`; `name()` returns + "Pickup"; it sets `pickup: true` on its options. +- `ShippingManager`: `createCollectionDriver()` → `createPickupDriver()`, + driver key `'collection'` → `'pickup'`. +- `ShippingMethodForm`'s driver select and the `shippingmethod.php` lang + files offer `pickup`. +- `shipping_methods.driver` values are persisted, so existing rows are + rewritten by the upgrade migration below. + +### Panel / demo data / tests + +- Panel: docblock and comment mentions in `OrderFulfilmentController` and + `FulfilmentCard.vue`; the `FulfilmentCard.test.ts` fixture method/labels. + No behavioural frontend change — labels come from the server. +- Admin: docblock mentions in `OrderFulfilments`. +- Demo data: `OrdersGenerator` creates the pickup fulfilment with + `method => 'pickup'`. +- Tests: `FulfilmentMethodTest` (including the registry-order expectation + `['digital', 'pickup', 'shipping']`), `FulfilmentTransitionTest`, panel + `OrderFulfilmentTest`, admin `OrderFulfilmentsTest`, factory state + call sites. + +## Alternatives considered + +- **Relabel only (keep persisted keys and class names, change English UI + strings).** No breaking change, but the API, database values, and state + names keep diverging from what the UI says, and the three-way `Collection` + class collision stands. The point of the rename is the vocabulary + developers and integrations touch, not just the label. +- **`local-pickup` (WooCommerce) or `in-store-pickup` (Magento) as the key.** + The qualifiers add nothing inside Lunar — the method is defined by the + customer picking the order up rather than where from — and Shopify's + unqualified `pickup` / `picked_up` is the closest match to our state names. +- **Rename the fulfilment side only, leave the table-rate-shipping driver.** + Rejected: the storefront-facing shipping option would still be labelled + "Collection" and the `collect` flag would feed a `pickup` method. Decided + to include it despite the extra v1 data migration. +- **Do nothing.** The overload and the ecosystem mismatch only get more + expensive to fix after v2 ships and third parties build on the state names. + +## Migration impact + +- **Database migrations**: none to the v2 baseline (the affected columns — + `fulfilments.method`, `fulfilments.state`, `order_lines.meta` — are + schema-unchanged; only the values we write change). One new migration in + the **upgrade** package rewriting `shipping_methods.driver` from + `'collection'` to `'pickup'` for v1 databases. +- **Existing v2 alpha databases** hold stale values (`method = 'collection'`, + `state = 'collected'` / `'ready-for-collection'`, `meta.collect`). Per the + alpha policy no change migration ships — re-seed (demo data regenerates). + This must land before v2 releases; afterwards it would require a data + migration. +- **Breaking changes / Rector**: + - `Lunar\Shipping\Drivers\ShippingMethods\Collection` exists in v1 — update + its `LunarSetList::V1_TO_V2_CLASS_RENAMES` entry to point at `...\Pickup`. + - `ShippingOption::$collect` exists in v1 — new Rector rule renaming the + `collect:` named argument on `ShippingOption` construction and + `->collect` property access to `pickup`. + - The core fulfilment method and state classes are v2-new (spec 0031), so + they need no v1 rules; v2-alpha consumers get the rename in release + notes. +- **Translation / locale impact**: key renames plus retranslated values in + core `states.php` + `fulfilment.php` and panel `orders.php` and admin + `order.php` (16 locales each), and table-rate-shipping `shippingmethod.php` + (14 shipped locales). +- **Filament / admin impact**: lang keys and docblocks only; the fulfil / + handed-over labels resolve per method key as before. + +## Open questions + +- Does any v1 order-creation path persist the shipping option's `collect` + flag into data the upgrade package carries over? Current understanding is + no — `meta['collect']` is stamped by v2's `CreateShippingLine`, and the + upgrade backfill (`2026_06_01_000009`) creates only `shipping`/`shipped` + fulfilments — but verify against v1 during implementation; if v1 does + persist it anywhere, the upgrade migration also rewrites that key. + +## References + +- `packages/core/src/Drivers/FulfilmentMethods/Collection.php`, + `packages/core/src/States/Fulfilment/{ReadyForCollection,Collected}.php` — the classes being renamed. +- `packages/core/src/Pipelines/Order/Creation/CreateShippingLine.php` — where the routing flag is stamped. +- `packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php` — the v1-era shipping driver. +- Shopify fulfilment event statuses `ready_for_pickup` / `picked_up`; WooCommerce "Local pickup"; Magento "In-Store Pickup" — the terminology being aligned to. +- [[0031-fulfilment-methods]] — introduced the method/state seam this renames. +- [[0022-order-fulfilments]] — the fulfilment model itself. + +## Implementation plan + +- [ ] Slice 1 — The rename across core, panel, admin, demo data, and table-rate-shipping: classes, persisted keys, the `pickup` flag, lang files in every locale, tests and fixtures, docblocks. +- [ ] Slice 2 — Upgrade path: `shipping_methods.driver` data migration, `LunarSetList` entry update, `ShippingOption` flag Rector rule, upgrade-suite tests. diff --git a/specs/README.md b/specs/README.md index 49d4bdd081..e1dbad6bd1 100644 --- a/specs/README.md +++ b/specs/README.md @@ -93,3 +93,5 @@ Each spec carries a `Status:` line in its frontmatter / header: | 0072 | Panel Discounts section | accepted | | 0073 | Split `AmountOff` into `PercentageOff` and `FixedAmountOff` | implemented | | 0074 | Panel global search (command palette) | implemented | +| 0075 | First staff account creation without the Filament admin | implemented | +| 0076 | Rename the collection fulfilment method to pickup | proposed | From 90ae8731a524e89568b5c0be16ad6df2df2c34f4 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 7 Sep 2026 13:55:14 +0100 Subject: [PATCH 2/3] spec: mark 0076 accepted Co-Authored-By: Claude Fable 5 --- specs/0076-rename-collection-fulfilment-to-pickup.md | 2 +- specs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/0076-rename-collection-fulfilment-to-pickup.md b/specs/0076-rename-collection-fulfilment-to-pickup.md index 2beed0b53f..c4befdc26b 100644 --- a/specs/0076-rename-collection-fulfilment-to-pickup.md +++ b/specs/0076-rename-collection-fulfilment-to-pickup.md @@ -1,6 +1,6 @@ # 0076 — Rename the collection fulfilment method to pickup -- Status: proposed +- Status: accepted - Author: Glenn Jacobs - Created: 2026-09-07 - TODO item: Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0076) diff --git a/specs/README.md b/specs/README.md index e1dbad6bd1..663f77e03d 100644 --- a/specs/README.md +++ b/specs/README.md @@ -94,4 +94,4 @@ Each spec carries a `Status:` line in its frontmatter / header: | 0073 | Split `AmountOff` into `PercentageOff` and `FixedAmountOff` | implemented | | 0074 | Panel global search (command palette) | implemented | | 0075 | First staff account creation without the Filament admin | implemented | -| 0076 | Rename the collection fulfilment method to pickup | proposed | +| 0076 | Rename the collection fulfilment method to pickup | accepted | From fc6e8e4b18203587193236ac5fda7fdb70878f15 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 7 Sep 2026 14:12:02 +0100 Subject: [PATCH 3/3] spec: renumber the pickup rename spec to 0077 Upstream landed the Filament admin and bridge hardening work as spec 0076 (#2708), so the pickup rename takes the next free number. Co-Authored-By: Claude Fable 5 --- TODO.md | 2 +- build/openapi/admin.json | 1682 ++++++++ build/openapi/storefront.json | 3634 +++++++++++++++++ ...rename-collection-fulfilment-to-pickup.md} | 4 +- specs/README.md | 3 +- 5 files changed, 5321 insertions(+), 4 deletions(-) create mode 100644 build/openapi/admin.json create mode 100644 build/openapi/storefront.json rename specs/{0076-rename-collection-fulfilment-to-pickup.md => 0077-rename-collection-fulfilment-to-pickup.md} (98%) diff --git a/TODO.md b/TODO.md index 47d7edceeb..1556679d21 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,7 @@ Items tagged _(judgement)_ are genuine line-calls worth revisiting. - Attribute field-type configuration schema — declarative config surface for field types in the panel settings (spec 0054) - Default professional customer notifications for the order lifecycle (spec 0036) _(judgement)_ - Bulk order operations — goal-oriented bulk actions on the orders table (spec 0026) -- Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0076) +- Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0077) - Order print templates — Print dropdown of selectable PDF templates, ships an Advice Note (spec 0027) - Cart/order line grouping — grouping key on the `*_lines` tables _(judgement)_ - Cart totals caching in the database — additive performance optimisation diff --git a/build/openapi/admin.json b/build/openapi/admin.json new file mode 100644 index 0000000000..a889a67143 --- /dev/null +++ b/build/openapi/admin.json @@ -0,0 +1,1682 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Admin API", + "version": "v1", + "description": "The Lunar admin API. Generated from the resource registry; add-on fields and endpoints appear automatically.", + "x-lunar-release": "dev", + "license": { + "name": "MIT", + "identifier": "MIT" + } + }, + "servers": [ + { + "url": "https://store.example/api/admin/v1", + "description": "Lunar" + } + ], + "security": [ + { + "apiKey": [] + } + ], + "tags": [ + { + "name": "products", + "x-group": "Products", + "description": "Every product in the catalogue, whatever its state, with raw attribute data and locale maps." + }, + { + "name": "variants", + "x-group": "Variants", + "description": "A variant of a product with its stock levels and prices." + }, + { + "name": "prices", + "x-group": "Prices", + "description": "A price of a variant for one currency, quantity break and customer group." + }, + { + "name": "brands", + "x-group": "Brands", + "description": "Every brand in the catalogue, whatever its state." + }, + { + "name": "api-keys", + "x-group": "API keys", + "description": "Credentials for the admin API. The token is returned once, when the key is issued; revoked keys stay listed for the audit trail." + } + ], + "paths": { + "/openapi.json": { + "get": { + "operationId": "openapi", + "x-hidden": true, + "summary": "Retrieve this OpenAPI document", + "responses": { + "200": { + "description": "Success." + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/products": { + "get": { + "operationId": "productsIndex", + "tags": [ + "products" + ], + "summary": "List products", + "parameters": [ + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `brand`: The brand, null when the product has none.\n- `variants`: Every variant of the product.\n- `variants.product`: The parent product.\n- `variants.product.brand`: The brand, null when the product has none.\n- `variants.product.variants`: Every variant of the product.\n- `variants.prices`: Every price of the variant.", + "x-lunar-includes": [ + "brand", + "variants", + "variants.product", + "variants.product.brand", + "variants.product.variants", + "variants.prices" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, status, brand_id, product_type_id, attribute_data, created_at, updated_at, brand, variants." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, status, description, short_description, created_at, updated_at." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, enabled, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, tax_ref, stock_on_hand, stock_available, created_at, updated_at, product, prices." + }, + "prices": { + "type": "string", + "description": "Comma-separated fields of prices to return: price, list_price, currency, min_quantity, customer_group_id." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "status": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by lifecycle state. Operators: eq, ne, in, not_in." + }, + "brand": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Products of the brand with this public id. Operators: eq, in." + }, + "sku": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "like": { + "type": "string" + } + }, + "additionalProperties": false + } + ], + "description": "Products with a variant matching this SKU. Operators: eq, in, like." + }, + "updated_at": { + "oneOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "object", + "properties": { + "gt": { + "type": "string", + "format": "date-time" + }, + "gte": { + "type": "string", + "format": "date-time" + }, + "lt": { + "type": "string", + "format": "date-time" + }, + "lte": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + } + ], + "description": "Products changed before or after an instant, for incremental sync. Operators: gt, gte, lt, lte." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `created_at`: By creation time.\n- `updated_at`: By last change.", + "x-lunar-sorts": [ + "created_at", + "updated_at" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + } + ], + "x-lunar-requires": [ + "catalog:read" + ], + "responses": { + "200": { + "description": "A page of products.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + }, + "meta": { + "type": "object", + "properties": { + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/products/{id}": { + "get": { + "operationId": "productsShow", + "tags": [ + "products" + ], + "summary": "Retrieve a product", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the product." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `brand`: The brand, null when the product has none.\n- `variants`: Every variant of the product.\n- `variants.product`: The parent product.\n- `variants.product.brand`: The brand, null when the product has none.\n- `variants.product.variants`: Every variant of the product.\n- `variants.prices`: Every price of the variant.", + "x-lunar-includes": [ + "brand", + "variants", + "variants.product", + "variants.product.brand", + "variants.product.variants", + "variants.prices" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, status, brand_id, product_type_id, attribute_data, created_at, updated_at, brand, variants." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, status, description, short_description, created_at, updated_at." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, enabled, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, tax_ref, stock_on_hand, stock_available, created_at, updated_at, product, prices." + }, + "prices": { + "type": "string", + "description": "Comma-separated fields of prices to return: price, list_price, currency, min_quantity, customer_group_id." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + } + ], + "x-lunar-requires": [ + "catalog:read" + ], + "responses": { + "200": { + "description": "The product.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Product" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/api-keys": { + "get": { + "operationId": "apiKeysIndex", + "tags": [ + "api-keys" + ], + "summary": "List API keys", + "parameters": [ + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "api-keys": { + "type": "string", + "description": "Comma-separated fields of api-keys to return: name, token_prefix, abilities, staff_id, active, last_used_at, expires_at, revoked_at, created_at, updated_at." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "like": { + "type": "string" + } + }, + "additionalProperties": false + } + ], + "description": "Match by name. Operators: eq, like." + }, + "active": { + "type": "boolean", + "description": "Only keys that are neither revoked nor expired when true. Operators: eq." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `name`: Alphabetical by name.\n- `created_at`: By issue time.\n- `last_used_at`: By last use.", + "x-lunar-sorts": [ + "name", + "created_at", + "last_used_at" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + } + ], + "x-lunar-requires": [ + "settings:manage-api-keys" + ], + "responses": { + "200": { + "description": "A page of API keys.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApiKey" + } + }, + "meta": { + "type": "object", + "properties": { + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "post": { + "operationId": "apiKeysStore", + "tags": [ + "api-keys" + ], + "summary": "Issue an API key", + "description": "Creates a key with the given abilities. The plaintext token is returned in this response only; store it securely.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "A label for the integration the key belongs to." + }, + "abilities": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Permission handles to grant, from the staff permission manifest, or * for every permission. At least one." + }, + "staff_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the staff member the key acts as. Omit for a service key." + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "When the key stops authenticating. Must be in the future; omit for no expiry." + } + }, + "required": [ + "name", + "abilities" + ] + } + } + } + }, + "x-lunar-requires": [ + "settings:manage-api-keys" + ], + "responses": { + "201": { + "description": "The issued key with its plaintext token.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/ApiKey" + }, + { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The plaintext bearer token. Store it now; it is not retrievable later." + } + }, + "required": [ + "token" + ] + } + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/api-keys/{id}": { + "get": { + "operationId": "apiKeysShow", + "tags": [ + "api-keys" + ], + "summary": "Retrieve an API key", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the API key." + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "api-keys": { + "type": "string", + "description": "Comma-separated fields of api-keys to return: name, token_prefix, abilities, staff_id, active, last_used_at, expires_at, revoked_at, created_at, updated_at." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + } + ], + "x-lunar-requires": [ + "settings:manage-api-keys" + ], + "responses": { + "200": { + "description": "The API key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiKey" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + }, + "delete": { + "operationId": "apiKeysDestroy", + "tags": [ + "api-keys" + ], + "summary": "Revoke an API key", + "description": "Stops the key authenticating. Revoked keys stay listed so the audit trail keeps its actor.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the resource." + } + ], + "x-lunar-requires": [ + "settings:manage-api-keys" + ], + "responses": { + "204": { + "description": "Done; no content." + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + } + }, + "components": { + "schemas": { + "Product": { + "type": "object", + "title": "Product", + "description": "Every product in the catalogue, whatever its state, with raw attribute data and locale maps.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the product." + }, + "type": { + "type": "string", + "const": "products", + "description": "The resource type." + }, + "name": { + "$ref": "#/components/schemas/TranslationMap", + "description": "The product name, keyed by locale." + }, + "description": { + "oneOf": [ + { + "$ref": "#/components/schemas/TranslationMap" + }, + { + "type": "null" + } + ], + "description": "The long description, keyed by locale." + }, + "short_description": { + "oneOf": [ + { + "$ref": "#/components/schemas/TranslationMap" + }, + { + "type": "null" + } + ], + "description": "The short description, keyed by locale." + }, + "status": { + "type": "string", + "description": "Lifecycle state: draft, published or archived." + }, + "brand_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the brand, or null." + }, + "product_type_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the product type." + }, + "attribute_data": { + "type": "object", + "additionalProperties": {}, + "description": "Raw attribute values keyed by handle; translatable attributes are locale maps." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the product was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the product was last updated." + }, + "brand": { + "oneOf": [ + { + "$ref": "#/components/schemas/Brand" + }, + { + "type": "null" + } + ], + "description": "The brand, null when the product has none. Present when included." + }, + "variants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Variant" + }, + "description": "Every variant of the product. Present when included." + } + }, + "required": [ + "id", + "type", + "name", + "status", + "attribute_data", + "created_at", + "updated_at" + ], + "x-lunar-type": "products" + }, + "Variant": { + "type": "object", + "title": "Variant", + "description": "A variant of a product with its stock levels and prices.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the variant." + }, + "type": { + "type": "string", + "const": "variants", + "description": "The resource type." + }, + "sku": { + "type": "string", + "description": "Stock keeping unit." + }, + "gtin": { + "type": [ + "string", + "null" + ], + "description": "Global Trade Item Number." + }, + "mpn": { + "type": [ + "string", + "null" + ], + "description": "Manufacturer Part Number." + }, + "ean": { + "type": [ + "string", + "null" + ], + "description": "European Article Number." + }, + "enabled": { + "type": "boolean", + "description": "Whether the variant is offered for sale." + }, + "unit_quantity": { + "type": "integer", + "description": "Units in one sellable quantity." + }, + "min_quantity": { + "type": "integer", + "description": "Smallest quantity that can be added to a cart." + }, + "quantity_increment": { + "type": "integer", + "description": "Step between allowed quantities." + }, + "shippable": { + "type": "boolean", + "description": "Whether the variant needs shipping." + }, + "selling_policy": { + "type": "string", + "enum": [ + "always", + "in_stock", + "in_stock_or_on_backorder" + ], + "description": "How the variant sells relative to its stock." + }, + "tax_ref": { + "type": [ + "string", + "null" + ], + "description": "Reference the tax driver uses to classify the variant." + }, + "stock_on_hand": { + "type": "integer", + "description": "Units physically in stock." + }, + "stock_available": { + "type": "integer", + "description": "Units available to sell after commitments and reservations." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the variant was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the variant was last updated." + }, + "product": { + "oneOf": [ + { + "$ref": "#/components/schemas/Product" + }, + { + "type": "null" + } + ], + "description": "The parent product. Present when included." + }, + "prices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Price" + }, + "description": "Every price of the variant. Present when included." + } + }, + "required": [ + "id", + "type", + "sku", + "enabled", + "unit_quantity", + "min_quantity", + "quantity_increment", + "shippable", + "selling_policy", + "stock_on_hand", + "stock_available", + "created_at", + "updated_at" + ], + "x-lunar-type": "variants" + }, + "Price": { + "type": "object", + "title": "Price", + "description": "A price of a variant for one currency, quantity break and customer group.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the price." + }, + "type": { + "type": "string", + "const": "prices", + "description": "The resource type." + }, + "price": { + "oneOf": [ + { + "$ref": "#/components/schemas/Money" + }, + { + "type": "null" + } + ], + "description": "The selling price." + }, + "list_price": { + "oneOf": [ + { + "$ref": "#/components/schemas/Money" + }, + { + "type": "null" + } + ], + "description": "The recommended or was price, when one is set." + }, + "currency": { + "type": [ + "string", + "null" + ], + "description": "ISO 4217 code of the price currency." + }, + "min_quantity": { + "type": "integer", + "description": "The quantity from which this price applies." + }, + "customer_group_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the customer group the price is for, or null for every group." + } + }, + "required": [ + "id", + "type", + "min_quantity" + ], + "x-lunar-type": "prices" + }, + "Brand": { + "type": "object", + "title": "Brand", + "description": "Every brand in the catalogue, whatever its state.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the brand." + }, + "type": { + "type": "string", + "const": "brands", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The brand name." + }, + "handle": { + "type": "string", + "description": "The unique handle." + }, + "status": { + "type": "string", + "description": "Lifecycle state, such as active or archived." + }, + "description": { + "oneOf": [ + { + "$ref": "#/components/schemas/TranslationMap" + }, + { + "type": "null" + } + ], + "description": "The long description, keyed by locale." + }, + "short_description": { + "oneOf": [ + { + "$ref": "#/components/schemas/TranslationMap" + }, + { + "type": "null" + } + ], + "description": "The short description, keyed by locale." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the brand was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the brand was last updated." + } + }, + "required": [ + "id", + "type", + "name", + "handle", + "status", + "created_at", + "updated_at" + ], + "x-lunar-type": "brands" + }, + "ApiKey": { + "type": "object", + "title": "API key", + "description": "Credentials for the admin API. The token is returned once, when the key is issued; revoked keys stay listed for the audit trail.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the api key." + }, + "type": { + "type": "string", + "const": "api-keys", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "A label for the integration the key belongs to." + }, + "token_prefix": { + "type": "string", + "description": "The first characters of the token, to identify a key without exposing it." + }, + "abilities": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Permission handles the key was granted, or * for every permission." + }, + "staff_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the staff member the key acts as, or null for a service key." + }, + "active": { + "type": "boolean", + "description": "Whether the key is neither revoked nor expired." + }, + "last_used_at": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "When the key last authenticated a request." + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "When the key stops authenticating, or null for no expiry." + }, + "revoked_at": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "When the key was revoked, or null while it is live." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the key was issued." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the key was last changed." + } + }, + "required": [ + "id", + "type", + "name", + "token_prefix", + "abilities", + "active", + "created_at", + "updated_at" + ], + "x-lunar-type": "api-keys" + }, + "Money": { + "type": "object", + "title": "Money", + "description": "A monetary amount in minor units with its currency, so arithmetic never needs to know the decimal places.", + "properties": { + "amount": { + "type": "integer", + "description": "The amount in minor units (pence, cents)." + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code." + }, + "decimal_places": { + "type": "integer", + "description": "Minor units per major unit, as a power of ten." + }, + "formatted": { + "type": [ + "string", + "null" + ], + "description": "The amount formatted for the request locale." + } + }, + "required": [ + "amount", + "currency", + "decimal_places", + "formatted" + ] + }, + "TranslationMap": { + "type": "object", + "title": "TranslationMap", + "description": "A translatable value: one entry per locale code.", + "additionalProperties": { + "type": [ + "string", + "null" + ] + } + }, + "Error": { + "type": "object", + "title": "Error", + "description": "One JSON:API error object.", + "properties": { + "status": { + "type": "string", + "description": "The HTTP status, as a string." + }, + "code": { + "type": "string", + "description": "A stable machine-readable code, such as unknown_filter or validation_failed." + }, + "title": { + "type": "string", + "description": "A short human-readable summary." + }, + "detail": { + "type": "string", + "description": "What went wrong for this occurrence." + }, + "source": { + "type": "object", + "description": "Where the error originated: a query parameter, a JSON pointer into the body, or a header.", + "properties": { + "parameter": { + "type": "string" + }, + "pointer": { + "type": "string" + }, + "header": { + "type": "string" + } + } + } + }, + "required": [ + "status", + "code", + "title" + ] + }, + "ErrorResponse": { + "type": "object", + "title": "ErrorResponse", + "description": "Every error response: one or more error objects.", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Error" + } + } + }, + "required": [ + "errors" + ] + }, + "PaginationMeta": { + "title": "PaginationMeta", + "description": "Page-number pagination, or cursor pagination when the request used page[cursor].", + "oneOf": [ + { + "type": "object", + "title": "PagePagination", + "properties": { + "page": { + "type": "integer", + "description": "The current page, from 1." + }, + "per_page": { + "type": "integer", + "description": "Items per page." + }, + "total": { + "type": "integer", + "description": "Total matching items." + }, + "last_page": { + "type": "integer", + "description": "The last page number." + } + }, + "required": [ + "page", + "per_page", + "total", + "last_page" + ] + }, + { + "type": "object", + "title": "CursorPagination", + "properties": { + "per_page": { + "type": "integer", + "description": "Items per page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Cursor for the next page, or null on the last." + }, + "prev_cursor": { + "type": [ + "string", + "null" + ], + "description": "Cursor for the previous page, or null on the first." + } + }, + "required": [ + "per_page", + "next_cursor", + "prev_cursor" + ] + } + ] + }, + "Links": { + "type": "object", + "title": "Links", + "description": "Navigation links. Only self is present on item responses.", + "properties": { + "self": { + "type": "string", + "format": "uri", + "description": "The URL of this response." + }, + "first": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "last": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "next": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "prev": { + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "self" + ] + } + }, + "responses": { + "Unauthorized": { + "description": "The credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "Forbidden": { + "description": "The credential lacks the ability this endpoint requires.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "NotFound": { + "description": "No resource with that id is visible to the request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "UnprocessableEntity": { + "description": "A query parameter, header or body field is invalid. Each error names its source.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "TooManyRequests": { + "description": "The rate limit was exceeded. Retry after the Retry-After header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "securitySchemes": { + "apiKey": { + "type": "http", + "scheme": "bearer", + "description": "An admin API key issued with `lunar:api:key create` or `POST /api-keys`, or a token of the guard named by `lunar.api.admin.guard`. Abilities gate endpoints and fields; see `x-lunar-requires`." + } + } + } +} diff --git a/build/openapi/storefront.json b/build/openapi/storefront.json new file mode 100644 index 0000000000..82df3742ac --- /dev/null +++ b/build/openapi/storefront.json @@ -0,0 +1,3634 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Storefront API", + "version": "v1", + "description": "The Lunar storefront API. Generated from the resource registry; add-on fields and endpoints appear automatically.", + "x-lunar-release": "dev", + "license": { + "name": "MIT", + "identifier": "MIT" + } + }, + "servers": [ + { + "url": "https://store.example/api/storefront/v1", + "description": "Lunar" + } + ], + "security": [], + "tags": [ + { + "name": "products", + "x-group": "Products", + "description": "Sellable products with their lowest price for the request. Only published products scheduled into the request channel and visible to its customer groups are served." + }, + { + "name": "variants", + "x-group": "Variants", + "description": "A sellable variant of a product with its stock and price for the request." + }, + { + "name": "product-option-values", + "x-group": "Product option values", + "description": "A value of a product option, such as Red for Colour, as carried by a variant." + }, + { + "name": "brands", + "x-group": "Brands", + "description": "Brands group products under a manufacturer or label. Only active brands are served." + }, + { + "name": "collections", + "x-group": "Collections", + "description": "Nested groupings of products, such as categories. Only collections visible in the request channel and customer groups are served." + }, + { + "name": "collection-groups", + "x-group": "Collection groups", + "description": "Named sets of collections, such as a main menu or a seasonal campaign." + }, + { + "name": "urls", + "x-group": "Urls", + "description": "A slug a product, collection or brand is reachable at, per language." + }, + { + "name": "carts", + "x-group": "Carts", + "description": "The calculated cart for the request. Carts are addressed by the signed X-Lunar-Cart token, not by id." + }, + { + "name": "cart-lines", + "x-group": "Cart lines", + "description": "A purchasable and quantity on a cart, with its calculated totals. Lines are embedded in carts and have no endpoints of their own." + }, + { + "name": "customers", + "x-group": "Customers", + "description": "The customer record behind an authenticated storefront user." + } + ], + "paths": { + "/openapi.json": { + "get": { + "operationId": "openapi", + "x-hidden": true, + "summary": "Retrieve this OpenAPI document", + "parameters": [ + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "Success." + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/products": { + "get": { + "operationId": "productsIndex", + "tags": [ + "products" + ], + "summary": "List products", + "parameters": [ + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `brand`: The brand, null when the product has none.\n- `brand.products`: Products of the brand visible to the request.\n- `brand.products.brand`: The brand, null when the product has none.\n- `brand.products.variants`: Every variant of the product.\n- `brand.products.collections`: Collections the product is in that are visible to the request.\n- `brand.products.urls`: Every URL of the product.\n- `brand.urls`: Every URL of the brand.\n- `variants`: Every variant of the product.\n- `variants.product`: The parent product.\n- `variants.product.brand`: The brand, null when the product has none.\n- `variants.product.variants`: Every variant of the product.\n- `variants.product.collections`: Collections the product is in that are visible to the request.\n- `variants.product.urls`: Every URL of the product.\n- `variants.values`: The option values that distinguish this variant.\n- `collections`: Collections the product is in that are visible to the request.\n- `collections.group`: The group the collection belongs to.\n- `collections.group.collections`: Collections in the group visible to the request.\n- `collections.parent`: The parent collection, null for a root.\n- `collections.parent.group`: The group the collection belongs to.\n- `collections.parent.parent`: The parent collection, null for a root.\n- `collections.parent.children`: Child collections visible to the request.\n- `collections.parent.products`: Products in the collection visible to the request.\n- `collections.parent.urls`: Every URL of the collection.\n- `collections.children`: Child collections visible to the request.\n- `collections.children.group`: The group the collection belongs to.\n- `collections.children.parent`: The parent collection, null for a root.\n- `collections.children.children`: Child collections visible to the request.\n- `collections.children.products`: Products in the collection visible to the request.\n- `collections.children.urls`: Every URL of the collection.\n- `collections.products`: Products in the collection visible to the request.\n- `collections.products.brand`: The brand, null when the product has none.\n- `collections.products.variants`: Every variant of the product.\n- `collections.products.collections`: Collections the product is in that are visible to the request.\n- `collections.products.urls`: Every URL of the product.\n- `collections.urls`: Every URL of the collection.\n- `urls`: Every URL of the product.", + "x-lunar-includes": [ + "brand", + "brand.products", + "brand.products.brand", + "brand.products.variants", + "brand.products.collections", + "brand.products.urls", + "brand.urls", + "variants", + "variants.product", + "variants.product.brand", + "variants.product.variants", + "variants.product.collections", + "variants.product.urls", + "variants.values", + "collections", + "collections.group", + "collections.group.collections", + "collections.parent", + "collections.parent.group", + "collections.parent.parent", + "collections.parent.children", + "collections.parent.products", + "collections.parent.urls", + "collections.children", + "collections.children.group", + "collections.children.parent", + "collections.children.children", + "collections.children.products", + "collections.children.urls", + "collections.products", + "collections.products.brand", + "collections.products.variants", + "collections.products.collections", + "collections.products.urls", + "collections.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "brand": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Products of the brand with this handle. Operators: eq, in." + }, + "collection": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Products in the collection with this handle. Operators: eq, in." + }, + "sku": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "like": { + "type": "string" + } + }, + "additionalProperties": false + } + ], + "description": "Products with a variant matching this SKU. Operators: eq, in, like." + }, + "price": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "integer" + }, + "gt": { + "type": "integer" + }, + "gte": { + "type": "integer" + }, + "lt": { + "type": "integer" + }, + "lte": { + "type": "integer" + } + }, + "additionalProperties": false + } + ], + "description": "Compare the single-unit base price in minor units of the request currency. Operators: eq, gt, gte, lt, lte." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `created_at`: By creation time.\n- `name`: Alphabetical by name in the request locale.", + "x-lunar-sorts": [ + "created_at", + "name" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "A page of products.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "channel", + "currency", + "locale", + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/products/{id}": { + "get": { + "operationId": "productsShow", + "tags": [ + "products" + ], + "summary": "Retrieve a product", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the product." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `brand`: The brand, null when the product has none.\n- `brand.products`: Products of the brand visible to the request.\n- `brand.products.brand`: The brand, null when the product has none.\n- `brand.products.variants`: Every variant of the product.\n- `brand.products.collections`: Collections the product is in that are visible to the request.\n- `brand.products.urls`: Every URL of the product.\n- `brand.urls`: Every URL of the brand.\n- `variants`: Every variant of the product.\n- `variants.product`: The parent product.\n- `variants.product.brand`: The brand, null when the product has none.\n- `variants.product.variants`: Every variant of the product.\n- `variants.product.collections`: Collections the product is in that are visible to the request.\n- `variants.product.urls`: Every URL of the product.\n- `variants.values`: The option values that distinguish this variant.\n- `collections`: Collections the product is in that are visible to the request.\n- `collections.group`: The group the collection belongs to.\n- `collections.group.collections`: Collections in the group visible to the request.\n- `collections.parent`: The parent collection, null for a root.\n- `collections.parent.group`: The group the collection belongs to.\n- `collections.parent.parent`: The parent collection, null for a root.\n- `collections.parent.children`: Child collections visible to the request.\n- `collections.parent.products`: Products in the collection visible to the request.\n- `collections.parent.urls`: Every URL of the collection.\n- `collections.children`: Child collections visible to the request.\n- `collections.children.group`: The group the collection belongs to.\n- `collections.children.parent`: The parent collection, null for a root.\n- `collections.children.children`: Child collections visible to the request.\n- `collections.children.products`: Products in the collection visible to the request.\n- `collections.children.urls`: Every URL of the collection.\n- `collections.products`: Products in the collection visible to the request.\n- `collections.products.brand`: The brand, null when the product has none.\n- `collections.products.variants`: Every variant of the product.\n- `collections.products.collections`: Collections the product is in that are visible to the request.\n- `collections.products.urls`: Every URL of the product.\n- `collections.urls`: Every URL of the collection.\n- `urls`: Every URL of the product.", + "x-lunar-includes": [ + "brand", + "brand.products", + "brand.products.brand", + "brand.products.variants", + "brand.products.collections", + "brand.products.urls", + "brand.urls", + "variants", + "variants.product", + "variants.product.brand", + "variants.product.variants", + "variants.product.collections", + "variants.product.urls", + "variants.values", + "collections", + "collections.group", + "collections.group.collections", + "collections.parent", + "collections.parent.group", + "collections.parent.parent", + "collections.parent.children", + "collections.parent.products", + "collections.parent.urls", + "collections.children", + "collections.children.group", + "collections.children.parent", + "collections.children.children", + "collections.children.products", + "collections.children.urls", + "collections.products", + "collections.products.brand", + "collections.products.variants", + "collections.products.collections", + "collections.products.urls", + "collections.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "The product.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Product" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/brands": { + "get": { + "operationId": "brandsIndex", + "tags": [ + "brands" + ], + "summary": "List brands", + "parameters": [ + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `products`: Products of the brand visible to the request.\n- `products.brand`: The brand, null when the product has none.\n- `products.brand.products`: Products of the brand visible to the request.\n- `products.brand.urls`: Every URL of the brand.\n- `products.variants`: Every variant of the product.\n- `products.variants.product`: The parent product.\n- `products.variants.values`: The option values that distinguish this variant.\n- `products.collections`: Collections the product is in that are visible to the request.\n- `products.collections.group`: The group the collection belongs to.\n- `products.collections.parent`: The parent collection, null for a root.\n- `products.collections.children`: Child collections visible to the request.\n- `products.collections.products`: Products in the collection visible to the request.\n- `products.collections.urls`: Every URL of the collection.\n- `products.urls`: Every URL of the product.\n- `urls`: Every URL of the brand.", + "x-lunar-includes": [ + "products", + "products.brand", + "products.brand.products", + "products.brand.urls", + "products.variants", + "products.variants.product", + "products.variants.values", + "products.collections", + "products.collections.group", + "products.collections.parent", + "products.collections.children", + "products.collections.products", + "products.collections.urls", + "products.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "handle": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by handle. Operators: eq, ne, in, not_in." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `name`: Alphabetical by name.\n- `created_at`: By creation time.", + "x-lunar-sorts": [ + "name", + "created_at" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "A page of brands.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Brand" + } + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "channel", + "currency", + "locale", + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/brands/{id}": { + "get": { + "operationId": "brandsShow", + "tags": [ + "brands" + ], + "summary": "Retrieve a brand", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the brand." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `products`: Products of the brand visible to the request.\n- `products.brand`: The brand, null when the product has none.\n- `products.brand.products`: Products of the brand visible to the request.\n- `products.brand.urls`: Every URL of the brand.\n- `products.variants`: Every variant of the product.\n- `products.variants.product`: The parent product.\n- `products.variants.values`: The option values that distinguish this variant.\n- `products.collections`: Collections the product is in that are visible to the request.\n- `products.collections.group`: The group the collection belongs to.\n- `products.collections.parent`: The parent collection, null for a root.\n- `products.collections.children`: Child collections visible to the request.\n- `products.collections.products`: Products in the collection visible to the request.\n- `products.collections.urls`: Every URL of the collection.\n- `products.urls`: Every URL of the product.\n- `urls`: Every URL of the brand.", + "x-lunar-includes": [ + "products", + "products.brand", + "products.brand.products", + "products.brand.urls", + "products.variants", + "products.variants.product", + "products.variants.values", + "products.collections", + "products.collections.group", + "products.collections.parent", + "products.collections.children", + "products.collections.products", + "products.collections.urls", + "products.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "The brand.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Brand" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/collections": { + "get": { + "operationId": "collectionsIndex", + "tags": [ + "collections" + ], + "summary": "List collections", + "parameters": [ + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `group`: The group the collection belongs to.\n- `group.collections`: Collections in the group visible to the request.\n- `group.collections.group`: The group the collection belongs to.\n- `group.collections.parent`: The parent collection, null for a root.\n- `group.collections.children`: Child collections visible to the request.\n- `group.collections.products`: Products in the collection visible to the request.\n- `group.collections.urls`: Every URL of the collection.\n- `parent`: The parent collection, null for a root.\n- `parent.group`: The group the collection belongs to.\n- `parent.group.collections`: Collections in the group visible to the request.\n- `parent.parent`: The parent collection, null for a root.\n- `parent.parent.group`: The group the collection belongs to.\n- `parent.parent.parent`: The parent collection, null for a root.\n- `parent.parent.children`: Child collections visible to the request.\n- `parent.parent.products`: Products in the collection visible to the request.\n- `parent.parent.urls`: Every URL of the collection.\n- `parent.children`: Child collections visible to the request.\n- `parent.children.group`: The group the collection belongs to.\n- `parent.children.parent`: The parent collection, null for a root.\n- `parent.children.children`: Child collections visible to the request.\n- `parent.children.products`: Products in the collection visible to the request.\n- `parent.children.urls`: Every URL of the collection.\n- `parent.products`: Products in the collection visible to the request.\n- `parent.products.brand`: The brand, null when the product has none.\n- `parent.products.variants`: Every variant of the product.\n- `parent.products.collections`: Collections the product is in that are visible to the request.\n- `parent.products.urls`: Every URL of the product.\n- `parent.urls`: Every URL of the collection.\n- `children`: Child collections visible to the request.\n- `children.group`: The group the collection belongs to.\n- `children.group.collections`: Collections in the group visible to the request.\n- `children.parent`: The parent collection, null for a root.\n- `children.parent.group`: The group the collection belongs to.\n- `children.parent.parent`: The parent collection, null for a root.\n- `children.parent.children`: Child collections visible to the request.\n- `children.parent.products`: Products in the collection visible to the request.\n- `children.parent.urls`: Every URL of the collection.\n- `children.children`: Child collections visible to the request.\n- `children.children.group`: The group the collection belongs to.\n- `children.children.parent`: The parent collection, null for a root.\n- `children.children.children`: Child collections visible to the request.\n- `children.children.products`: Products in the collection visible to the request.\n- `children.children.urls`: Every URL of the collection.\n- `children.products`: Products in the collection visible to the request.\n- `children.products.brand`: The brand, null when the product has none.\n- `children.products.variants`: Every variant of the product.\n- `children.products.collections`: Collections the product is in that are visible to the request.\n- `children.products.urls`: Every URL of the product.\n- `children.urls`: Every URL of the collection.\n- `products`: Products in the collection visible to the request.\n- `products.brand`: The brand, null when the product has none.\n- `products.brand.products`: Products of the brand visible to the request.\n- `products.brand.urls`: Every URL of the brand.\n- `products.variants`: Every variant of the product.\n- `products.variants.product`: The parent product.\n- `products.variants.values`: The option values that distinguish this variant.\n- `products.collections`: Collections the product is in that are visible to the request.\n- `products.collections.group`: The group the collection belongs to.\n- `products.collections.parent`: The parent collection, null for a root.\n- `products.collections.children`: Child collections visible to the request.\n- `products.collections.products`: Products in the collection visible to the request.\n- `products.collections.urls`: Every URL of the collection.\n- `products.urls`: Every URL of the product.\n- `urls`: Every URL of the collection.", + "x-lunar-includes": [ + "group", + "group.collections", + "group.collections.group", + "group.collections.parent", + "group.collections.children", + "group.collections.products", + "group.collections.urls", + "parent", + "parent.group", + "parent.group.collections", + "parent.parent", + "parent.parent.group", + "parent.parent.parent", + "parent.parent.children", + "parent.parent.products", + "parent.parent.urls", + "parent.children", + "parent.children.group", + "parent.children.parent", + "parent.children.children", + "parent.children.products", + "parent.children.urls", + "parent.products", + "parent.products.brand", + "parent.products.variants", + "parent.products.collections", + "parent.products.urls", + "parent.urls", + "children", + "children.group", + "children.group.collections", + "children.parent", + "children.parent.group", + "children.parent.parent", + "children.parent.children", + "children.parent.products", + "children.parent.urls", + "children.children", + "children.children.group", + "children.children.parent", + "children.children.children", + "children.children.products", + "children.children.urls", + "children.products", + "children.products.brand", + "children.products.variants", + "children.products.collections", + "children.products.urls", + "children.urls", + "products", + "products.brand", + "products.brand.products", + "products.brand.urls", + "products.variants", + "products.variants.product", + "products.variants.values", + "products.collections", + "products.collections.group", + "products.collections.parent", + "products.collections.children", + "products.collections.products", + "products.collections.urls", + "products.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "handle": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by handle. Operators: eq, ne, in, not_in." + }, + "group": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Collections in the group with this handle. Operators: eq, in." + }, + "parent": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Children of the collection with this public id. Operators: eq, in." + }, + "root": { + "type": "boolean", + "description": "Only root collections when true. Operators: eq." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `created_at`: By creation time.\n- `name`: Alphabetical by name in the request locale.", + "x-lunar-sorts": [ + "created_at", + "name" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "A page of collections.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Collection" + } + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "channel", + "currency", + "locale", + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/collections/{id}": { + "get": { + "operationId": "collectionsShow", + "tags": [ + "collections" + ], + "summary": "Retrieve a collection", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the collection." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `group`: The group the collection belongs to.\n- `group.collections`: Collections in the group visible to the request.\n- `group.collections.group`: The group the collection belongs to.\n- `group.collections.parent`: The parent collection, null for a root.\n- `group.collections.children`: Child collections visible to the request.\n- `group.collections.products`: Products in the collection visible to the request.\n- `group.collections.urls`: Every URL of the collection.\n- `parent`: The parent collection, null for a root.\n- `parent.group`: The group the collection belongs to.\n- `parent.group.collections`: Collections in the group visible to the request.\n- `parent.parent`: The parent collection, null for a root.\n- `parent.parent.group`: The group the collection belongs to.\n- `parent.parent.parent`: The parent collection, null for a root.\n- `parent.parent.children`: Child collections visible to the request.\n- `parent.parent.products`: Products in the collection visible to the request.\n- `parent.parent.urls`: Every URL of the collection.\n- `parent.children`: Child collections visible to the request.\n- `parent.children.group`: The group the collection belongs to.\n- `parent.children.parent`: The parent collection, null for a root.\n- `parent.children.children`: Child collections visible to the request.\n- `parent.children.products`: Products in the collection visible to the request.\n- `parent.children.urls`: Every URL of the collection.\n- `parent.products`: Products in the collection visible to the request.\n- `parent.products.brand`: The brand, null when the product has none.\n- `parent.products.variants`: Every variant of the product.\n- `parent.products.collections`: Collections the product is in that are visible to the request.\n- `parent.products.urls`: Every URL of the product.\n- `parent.urls`: Every URL of the collection.\n- `children`: Child collections visible to the request.\n- `children.group`: The group the collection belongs to.\n- `children.group.collections`: Collections in the group visible to the request.\n- `children.parent`: The parent collection, null for a root.\n- `children.parent.group`: The group the collection belongs to.\n- `children.parent.parent`: The parent collection, null for a root.\n- `children.parent.children`: Child collections visible to the request.\n- `children.parent.products`: Products in the collection visible to the request.\n- `children.parent.urls`: Every URL of the collection.\n- `children.children`: Child collections visible to the request.\n- `children.children.group`: The group the collection belongs to.\n- `children.children.parent`: The parent collection, null for a root.\n- `children.children.children`: Child collections visible to the request.\n- `children.children.products`: Products in the collection visible to the request.\n- `children.children.urls`: Every URL of the collection.\n- `children.products`: Products in the collection visible to the request.\n- `children.products.brand`: The brand, null when the product has none.\n- `children.products.variants`: Every variant of the product.\n- `children.products.collections`: Collections the product is in that are visible to the request.\n- `children.products.urls`: Every URL of the product.\n- `children.urls`: Every URL of the collection.\n- `products`: Products in the collection visible to the request.\n- `products.brand`: The brand, null when the product has none.\n- `products.brand.products`: Products of the brand visible to the request.\n- `products.brand.urls`: Every URL of the brand.\n- `products.variants`: Every variant of the product.\n- `products.variants.product`: The parent product.\n- `products.variants.values`: The option values that distinguish this variant.\n- `products.collections`: Collections the product is in that are visible to the request.\n- `products.collections.group`: The group the collection belongs to.\n- `products.collections.parent`: The parent collection, null for a root.\n- `products.collections.children`: Child collections visible to the request.\n- `products.collections.products`: Products in the collection visible to the request.\n- `products.collections.urls`: Every URL of the collection.\n- `products.urls`: Every URL of the product.\n- `urls`: Every URL of the collection.", + "x-lunar-includes": [ + "group", + "group.collections", + "group.collections.group", + "group.collections.parent", + "group.collections.children", + "group.collections.products", + "group.collections.urls", + "parent", + "parent.group", + "parent.group.collections", + "parent.parent", + "parent.parent.group", + "parent.parent.parent", + "parent.parent.children", + "parent.parent.products", + "parent.parent.urls", + "parent.children", + "parent.children.group", + "parent.children.parent", + "parent.children.children", + "parent.children.products", + "parent.children.urls", + "parent.products", + "parent.products.brand", + "parent.products.variants", + "parent.products.collections", + "parent.products.urls", + "parent.urls", + "children", + "children.group", + "children.group.collections", + "children.parent", + "children.parent.group", + "children.parent.parent", + "children.parent.children", + "children.parent.products", + "children.parent.urls", + "children.children", + "children.children.group", + "children.children.parent", + "children.children.children", + "children.children.products", + "children.children.urls", + "children.products", + "children.products.brand", + "children.products.variants", + "children.products.collections", + "children.products.urls", + "children.urls", + "products", + "products.brand", + "products.brand.products", + "products.brand.urls", + "products.variants", + "products.variants.product", + "products.variants.values", + "products.collections", + "products.collections.group", + "products.collections.parent", + "products.collections.children", + "products.collections.products", + "products.collections.urls", + "products.urls", + "urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + }, + "product-option-values": { + "type": "string", + "description": "Comma-separated fields of product-option-values to return: name, position, option, option_id." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "The collection.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Collection" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/collection-groups": { + "get": { + "operationId": "collectionGroupsIndex", + "tags": [ + "collection-groups" + ], + "summary": "List collection groups", + "parameters": [ + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `collections`: Collections in the group visible to the request.\n- `collections.group`: The group the collection belongs to.\n- `collections.group.collections`: Collections in the group visible to the request.\n- `collections.parent`: The parent collection, null for a root.\n- `collections.parent.group`: The group the collection belongs to.\n- `collections.parent.parent`: The parent collection, null for a root.\n- `collections.parent.children`: Child collections visible to the request.\n- `collections.parent.products`: Products in the collection visible to the request.\n- `collections.parent.urls`: Every URL of the collection.\n- `collections.children`: Child collections visible to the request.\n- `collections.children.group`: The group the collection belongs to.\n- `collections.children.parent`: The parent collection, null for a root.\n- `collections.children.children`: Child collections visible to the request.\n- `collections.children.products`: Products in the collection visible to the request.\n- `collections.children.urls`: Every URL of the collection.\n- `collections.products`: Products in the collection visible to the request.\n- `collections.products.brand`: The brand, null when the product has none.\n- `collections.products.variants`: Every variant of the product.\n- `collections.products.collections`: Collections the product is in that are visible to the request.\n- `collections.products.urls`: Every URL of the product.\n- `collections.urls`: Every URL of the collection.", + "x-lunar-includes": [ + "collections", + "collections.group", + "collections.group.collections", + "collections.parent", + "collections.parent.group", + "collections.parent.parent", + "collections.parent.children", + "collections.parent.products", + "collections.parent.urls", + "collections.children", + "collections.children.group", + "collections.children.parent", + "collections.children.children", + "collections.children.products", + "collections.children.urls", + "collections.products", + "collections.products.brand", + "collections.products.variants", + "collections.products.collections", + "collections.products.urls", + "collections.urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "name": "filter", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by public id. Operators: eq, ne, in, not_in." + }, + "handle": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "ne": { + "type": "string" + }, + "in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + }, + "not_in": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma-separated values." + } + ] + } + }, + "additionalProperties": false + } + ], + "description": "Match by handle. Operators: eq, ne, in, not_in." + } + }, + "additionalProperties": false + }, + "description": "Filters, for example `filter[handle]=acme` or `filter[price][gte]=1000`. `filter[name]=value` is shorthand for the `eq` operator." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sort keys; prefix a key with `-` to sort descending.\n\n- `name`: Alphabetical by name.\n- `created_at`: By creation time.", + "x-lunar-sorts": [ + "name", + "created_at" + ] + }, + { + "name": "page[number]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "The page to return." + }, + { + "name": "page[size]", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 15 + }, + "description": "Items per page." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "A page of collection groups.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CollectionGroup" + } + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + } + }, + "required": [ + "channel", + "currency", + "locale", + "pagination" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta", + "links" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/collection-groups/{id}": { + "get": { + "operationId": "collectionGroupsShow", + "tags": [ + "collection-groups" + ], + "summary": "Retrieve a collection group", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The public id of the collection group." + }, + { + "name": "include", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated related resources to embed, nested with dots up to 3 levels.\n\n- `collections`: Collections in the group visible to the request.\n- `collections.group`: The group the collection belongs to.\n- `collections.group.collections`: Collections in the group visible to the request.\n- `collections.parent`: The parent collection, null for a root.\n- `collections.parent.group`: The group the collection belongs to.\n- `collections.parent.parent`: The parent collection, null for a root.\n- `collections.parent.children`: Child collections visible to the request.\n- `collections.parent.products`: Products in the collection visible to the request.\n- `collections.parent.urls`: Every URL of the collection.\n- `collections.children`: Child collections visible to the request.\n- `collections.children.group`: The group the collection belongs to.\n- `collections.children.parent`: The parent collection, null for a root.\n- `collections.children.children`: Child collections visible to the request.\n- `collections.children.products`: Products in the collection visible to the request.\n- `collections.children.urls`: Every URL of the collection.\n- `collections.products`: Products in the collection visible to the request.\n- `collections.products.brand`: The brand, null when the product has none.\n- `collections.products.variants`: Every variant of the product.\n- `collections.products.collections`: Collections the product is in that are visible to the request.\n- `collections.products.urls`: Every URL of the product.\n- `collections.urls`: Every URL of the collection.", + "x-lunar-includes": [ + "collections", + "collections.group", + "collections.group.collections", + "collections.parent", + "collections.parent.group", + "collections.parent.parent", + "collections.parent.children", + "collections.parent.products", + "collections.parent.urls", + "collections.children", + "collections.children.group", + "collections.children.parent", + "collections.children.children", + "collections.children.products", + "collections.children.urls", + "collections.products", + "collections.products.brand", + "collections.products.variants", + "collections.products.collections", + "collections.products.urls", + "collections.urls" + ] + }, + { + "name": "fields", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "schema": { + "type": "object", + "properties": { + "collection-groups": { + "type": "string", + "description": "Comma-separated fields of collection-groups to return: name, handle, created_at, updated_at, collections." + }, + "collections": { + "type": "string", + "description": "Comma-separated fields of collections to return: name, handle, description, short_description, slug, parent_id, group_id, attributes, created_at, updated_at, group, parent, children, products, urls." + }, + "products": { + "type": "string", + "description": "Comma-separated fields of products to return: name, description, short_description, slug, product_type, brand_id, attributes, price, created_at, updated_at, brand, variants, collections, urls." + }, + "urls": { + "type": "string", + "description": "Comma-separated fields of urls to return: slug, default, language." + }, + "brands": { + "type": "string", + "description": "Comma-separated fields of brands to return: name, handle, description, short_description, slug, attributes, created_at, updated_at, products, urls." + }, + "variants": { + "type": "string", + "description": "Comma-separated fields of variants to return: sku, gtin, mpn, ean, unit_quantity, min_quantity, quantity_increment, shippable, selling_policy, stock, purchasable, price, product, values." + } + } + }, + "description": "Sparse fieldsets per resource type, for example `fields[products]=name,slug`. `id` and `type` are always returned." + }, + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "The collection group.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CollectionGroup" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/cart": { + "get": { + "operationId": "cartsShow", + "tags": [ + "carts" + ], + "summary": "Retrieve the current cart", + "description": "Returns the cart identified by the X-Lunar-Cart header, calculated for the request context. `data` is null when the request carries no cart token.", + "parameters": [ + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "responses": { + "200": { + "description": "The current cart, or `data: null` without a cart token.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Cart" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + }, + "/cart/lines": { + "post": { + "operationId": "cartsLinesStore", + "tags": [ + "carts" + ], + "summary": "Add a line to the cart", + "description": "Adds a variant to the cart, creating the cart when the request carries no X-Lunar-Cart token. Adding a variant already in the cart increments its line. The response carries the cart token on X-Lunar-Cart; send it on every later cart request.", + "parameters": [ + { + "$ref": "#/components/parameters/XLunarChannel" + }, + { + "$ref": "#/components/parameters/XLunarCurrency" + }, + { + "$ref": "#/components/parameters/XLunarCart" + }, + { + "$ref": "#/components/parameters/AcceptLanguage" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "purchasable_id": { + "type": "string", + "description": "Public id of the variant to add. It must be enabled and its product visible to the request." + }, + "quantity": { + "type": "integer", + "minimum": 1, + "description": "Units to add; defaults to 1." + }, + "meta": { + "type": [ + "array", + "null" + ], + "items": {}, + "description": "Arbitrary metadata stored on the line." + } + }, + "required": [ + "purchasable_id" + ] + } + } + } + }, + "responses": { + "201": { + "description": "The cart after the line was added.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Cart" + }, + "meta": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Handle of the channel the response was served for." + }, + "currency": { + "type": "string", + "description": "ISO 4217 code of the currency prices are in." + }, + "locale": { + "type": "string", + "description": "The locale translatable fields were resolved in." + } + }, + "required": [ + "channel", + "currency", + "locale" + ] + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "required": [ + "data", + "meta" + ] + } + } + }, + "headers": { + "X-Lunar-Channel": { + "$ref": "#/components/headers/X-Lunar-Channel" + }, + "X-Lunar-Currency": { + "$ref": "#/components/headers/X-Lunar-Currency" + }, + "Content-Language": { + "$ref": "#/components/headers/Content-Language" + }, + "X-Lunar-Cart": { + "$ref": "#/components/headers/X-Lunar-Cart" + } + } + }, + "422": { + "$ref": "#/components/responses/UnprocessableEntity" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + } + } + } + } + }, + "components": { + "schemas": { + "Product": { + "type": "object", + "title": "Product", + "description": "Sellable products with their lowest price for the request. Only published products scheduled into the request channel and visible to its customer groups are served.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the product." + }, + "type": { + "type": "string", + "const": "products", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The product name in the request locale." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "The long description in the request locale." + }, + "short_description": { + "type": [ + "string", + "null" + ], + "description": "The short description in the request locale." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The slug of the default URL, or null when the product has none." + }, + "product_type": { + "type": [ + "string", + "null" + ], + "description": "Name of the product type." + }, + "brand_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the brand, or null." + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "description": "Attribute values keyed by handle, translated into the request locale." + }, + "price": { + "oneOf": [ + { + "$ref": "#/components/schemas/Money" + }, + { + "type": "null" + } + ], + "description": "The lowest single-unit variant price for the request currency and customer groups, or null when no variant is priced." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the product was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the product was last updated." + }, + "brand": { + "oneOf": [ + { + "$ref": "#/components/schemas/Brand" + }, + { + "type": "null" + } + ], + "description": "The brand, null when the product has none. Present when included." + }, + "variants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Variant" + }, + "description": "Every variant of the product. Present when included." + }, + "collections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Collection" + }, + "description": "Collections the product is in that are visible to the request. Present when included." + }, + "urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Url" + }, + "description": "Every URL of the product. Present when included." + } + }, + "required": [ + "id", + "type", + "name", + "attributes", + "created_at", + "updated_at" + ], + "x-lunar-type": "products" + }, + "Variant": { + "type": "object", + "title": "Variant", + "description": "A sellable variant of a product with its stock and price for the request.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the variant." + }, + "type": { + "type": "string", + "const": "variants", + "description": "The resource type." + }, + "sku": { + "type": "string", + "description": "Stock keeping unit." + }, + "gtin": { + "type": [ + "string", + "null" + ], + "description": "Global Trade Item Number." + }, + "mpn": { + "type": [ + "string", + "null" + ], + "description": "Manufacturer Part Number." + }, + "ean": { + "type": [ + "string", + "null" + ], + "description": "European Article Number." + }, + "unit_quantity": { + "type": "integer", + "description": "Units in one sellable quantity." + }, + "min_quantity": { + "type": "integer", + "description": "Smallest quantity that can be added to a cart." + }, + "quantity_increment": { + "type": "integer", + "description": "Step between allowed quantities." + }, + "shippable": { + "type": "boolean", + "description": "Whether the variant needs shipping." + }, + "selling_policy": { + "type": "string", + "enum": [ + "always", + "in_stock", + "in_stock_or_on_backorder" + ], + "description": "How the variant sells relative to its stock." + }, + "stock": { + "type": "integer", + "description": "Units available to sell." + }, + "purchasable": { + "type": "boolean", + "description": "Whether the variant can be added to a cart right now." + }, + "price": { + "oneOf": [ + { + "$ref": "#/components/schemas/Money" + }, + { + "type": "null" + } + ], + "description": "The single-unit price for the request currency and customer groups, or null when unpriced." + }, + "product": { + "oneOf": [ + { + "$ref": "#/components/schemas/Product" + }, + { + "type": "null" + } + ], + "description": "The parent product. Present when included." + }, + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductOptionValue" + }, + "description": "The option values that distinguish this variant. Present when included." + } + }, + "required": [ + "id", + "type", + "sku", + "unit_quantity", + "min_quantity", + "quantity_increment", + "shippable", + "selling_policy", + "stock", + "purchasable" + ], + "x-lunar-type": "variants" + }, + "ProductOptionValue": { + "type": "object", + "title": "Product option value", + "description": "A value of a product option, such as Red for Colour, as carried by a variant.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the product option value." + }, + "type": { + "type": "string", + "const": "product-option-values", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The value name in the request locale, such as Red." + }, + "position": { + "type": "integer", + "description": "Display order within the option." + }, + "option": { + "type": [ + "string", + "null" + ], + "description": "The option name in the request locale, such as Colour." + }, + "option_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the option the value belongs to." + } + }, + "required": [ + "id", + "type", + "name", + "position" + ], + "x-lunar-type": "product-option-values" + }, + "Brand": { + "type": "object", + "title": "Brand", + "description": "Brands group products under a manufacturer or label. Only active brands are served.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the brand." + }, + "type": { + "type": "string", + "const": "brands", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The brand name." + }, + "handle": { + "type": "string", + "description": "The unique handle, used by the handle filter." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "The long description in the request locale." + }, + "short_description": { + "type": [ + "string", + "null" + ], + "description": "The short description in the request locale." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The slug of the default URL, or null when the brand has none." + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "description": "Attribute values keyed by handle, translated into the request locale." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the brand was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the brand was last updated." + }, + "products": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + }, + "description": "Products of the brand visible to the request. Present when included." + }, + "urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Url" + }, + "description": "Every URL of the brand. Present when included." + } + }, + "required": [ + "id", + "type", + "name", + "handle", + "attributes", + "created_at", + "updated_at" + ], + "x-lunar-type": "brands" + }, + "Collection": { + "type": "object", + "title": "Collection", + "description": "Nested groupings of products, such as categories. Only collections visible in the request channel and customer groups are served.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the collection." + }, + "type": { + "type": "string", + "const": "collections", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The collection name in the request locale." + }, + "handle": { + "type": "string", + "description": "The unique handle, used by the handle filter." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "The long description in the request locale." + }, + "short_description": { + "type": [ + "string", + "null" + ], + "description": "The short description in the request locale." + }, + "slug": { + "type": [ + "string", + "null" + ], + "description": "The slug of the default URL, or null when the collection has none." + }, + "parent_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the parent collection; null for a root collection." + }, + "group_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the collection group." + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "description": "Attribute values keyed by handle, translated into the request locale." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the collection was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the collection was last updated." + }, + "group": { + "oneOf": [ + { + "$ref": "#/components/schemas/CollectionGroup" + }, + { + "type": "null" + } + ], + "description": "The group the collection belongs to. Present when included." + }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/Collection" + }, + { + "type": "null" + } + ], + "description": "The parent collection, null for a root. Present when included." + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Collection" + }, + "description": "Child collections visible to the request. Present when included." + }, + "products": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + }, + "description": "Products in the collection visible to the request. Present when included." + }, + "urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Url" + }, + "description": "Every URL of the collection. Present when included." + } + }, + "required": [ + "id", + "type", + "name", + "handle", + "attributes", + "created_at", + "updated_at" + ], + "x-lunar-type": "collections" + }, + "CollectionGroup": { + "type": "object", + "title": "Collection group", + "description": "Named sets of collections, such as a main menu or a seasonal campaign.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the collection group." + }, + "type": { + "type": "string", + "const": "collection-groups", + "description": "The resource type." + }, + "name": { + "type": "string", + "description": "The group name." + }, + "handle": { + "type": "string", + "description": "The unique handle, used by the handle filter." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the group was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the group was last updated." + }, + "collections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Collection" + }, + "description": "Collections in the group visible to the request. Present when included." + } + }, + "required": [ + "id", + "type", + "name", + "handle", + "created_at", + "updated_at" + ], + "x-lunar-type": "collection-groups" + }, + "Url": { + "type": "object", + "title": "Url", + "description": "A slug a product, collection or brand is reachable at, per language.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the url." + }, + "type": { + "type": "string", + "const": "urls", + "description": "The resource type." + }, + "slug": { + "type": "string", + "description": "The URL slug." + }, + "default": { + "type": "boolean", + "description": "Whether this is the canonical URL for its language." + }, + "language": { + "type": [ + "string", + "null" + ], + "description": "Language code the URL belongs to." + } + }, + "required": [ + "id", + "type", + "slug", + "default" + ], + "x-lunar-type": "urls" + }, + "Cart": { + "type": "object", + "title": "Cart", + "description": "The calculated cart for the request. Carts are addressed by the signed X-Lunar-Cart token, not by id.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the cart." + }, + "type": { + "type": "string", + "const": "carts", + "description": "The resource type." + }, + "currency": { + "type": [ + "string", + "null" + ], + "description": "ISO 4217 code of the cart currency." + }, + "channel": { + "type": [ + "string", + "null" + ], + "description": "Handle of the channel the cart belongs to." + }, + "coupon_code": { + "type": [ + "string", + "null" + ], + "description": "The coupon code applied to the cart, if any." + }, + "lines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CartLine" + }, + "description": "The cart lines, always embedded." + }, + "sub_total": { + "$ref": "#/components/schemas/Money", + "description": "Sum of line sub totals, before discounts and tax." + }, + "discount_total": { + "$ref": "#/components/schemas/Money", + "description": "Total discount across the cart." + }, + "shipping_total": { + "$ref": "#/components/schemas/Money", + "description": "Shipping cost, including tax." + }, + "tax_total": { + "$ref": "#/components/schemas/Money", + "description": "Total tax across the cart." + }, + "total": { + "$ref": "#/components/schemas/Money", + "description": "Amount payable." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the cart was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the cart was last changed." + } + }, + "required": [ + "id", + "type", + "lines", + "sub_total", + "discount_total", + "shipping_total", + "tax_total", + "total", + "created_at", + "updated_at" + ], + "x-lunar-type": "carts" + }, + "CartLine": { + "type": "object", + "title": "Cart line", + "description": "A purchasable and quantity on a cart, with its calculated totals. Lines are embedded in carts and have no endpoints of their own.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the cart line." + }, + "type": { + "type": "string", + "const": "cart-lines", + "description": "The resource type." + }, + "quantity": { + "type": "integer", + "description": "Units of the purchasable on the line." + }, + "purchasable_type": { + "type": "string", + "description": "The purchasable morph alias, for example product_variant." + }, + "purchasable_id": { + "type": [ + "string", + "null" + ], + "description": "Public id of the purchasable." + }, + "identifier": { + "type": [ + "string", + "null" + ], + "description": "The purchasable identifier, such as the variant SKU." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "The purchasable description, such as the product name." + }, + "unit_price": { + "$ref": "#/components/schemas/Money", + "description": "Price of one unit before discounts." + }, + "sub_total": { + "$ref": "#/components/schemas/Money", + "description": "Unit price times quantity, before discounts and tax." + }, + "discount_total": { + "$ref": "#/components/schemas/Money", + "description": "Discount applied to the line." + }, + "tax_total": { + "$ref": "#/components/schemas/Money", + "description": "Tax on the line." + }, + "total": { + "$ref": "#/components/schemas/Money", + "description": "Line total after discounts and tax." + }, + "meta": { + "description": "Arbitrary metadata attached when the line was added." + } + }, + "required": [ + "id", + "type", + "quantity", + "purchasable_type", + "unit_price", + "sub_total", + "discount_total", + "tax_total", + "total" + ], + "x-lunar-type": "cart-lines" + }, + "Customer": { + "type": "object", + "title": "Customer", + "description": "The customer record behind an authenticated storefront user.", + "properties": { + "id": { + "type": "string", + "description": "The public id of the customer." + }, + "type": { + "type": "string", + "const": "customers", + "description": "The resource type." + }, + "title": { + "type": [ + "string", + "null" + ], + "description": "Honorific, such as Mr or Dr." + }, + "first_name": { + "type": "string", + "description": "Given name." + }, + "last_name": { + "type": "string", + "description": "Family name." + }, + "company_name": { + "type": [ + "string", + "null" + ], + "description": "Company the customer buys on behalf of." + }, + "tax_identifier": { + "type": [ + "string", + "null" + ], + "description": "VAT or tax registration number." + }, + "account_ref": { + "type": [ + "string", + "null" + ], + "description": "The account reference in an external system." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the customer record was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the customer record was last updated." + } + }, + "required": [ + "id", + "type", + "first_name", + "last_name", + "created_at", + "updated_at" + ], + "x-lunar-type": "customers" + }, + "Money": { + "type": "object", + "title": "Money", + "description": "A monetary amount in minor units with its currency, so arithmetic never needs to know the decimal places.", + "properties": { + "amount": { + "type": "integer", + "description": "The amount in minor units (pence, cents)." + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code." + }, + "decimal_places": { + "type": "integer", + "description": "Minor units per major unit, as a power of ten." + }, + "formatted": { + "type": [ + "string", + "null" + ], + "description": "The amount formatted for the request locale." + } + }, + "required": [ + "amount", + "currency", + "decimal_places", + "formatted" + ] + }, + "Error": { + "type": "object", + "title": "Error", + "description": "One JSON:API error object.", + "properties": { + "status": { + "type": "string", + "description": "The HTTP status, as a string." + }, + "code": { + "type": "string", + "description": "A stable machine-readable code, such as unknown_filter or validation_failed." + }, + "title": { + "type": "string", + "description": "A short human-readable summary." + }, + "detail": { + "type": "string", + "description": "What went wrong for this occurrence." + }, + "source": { + "type": "object", + "description": "Where the error originated: a query parameter, a JSON pointer into the body, or a header.", + "properties": { + "parameter": { + "type": "string" + }, + "pointer": { + "type": "string" + }, + "header": { + "type": "string" + } + } + } + }, + "required": [ + "status", + "code", + "title" + ] + }, + "ErrorResponse": { + "type": "object", + "title": "ErrorResponse", + "description": "Every error response: one or more error objects.", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Error" + } + } + }, + "required": [ + "errors" + ] + }, + "PaginationMeta": { + "title": "PaginationMeta", + "description": "Page-number pagination, or cursor pagination when the request used page[cursor].", + "oneOf": [ + { + "type": "object", + "title": "PagePagination", + "properties": { + "page": { + "type": "integer", + "description": "The current page, from 1." + }, + "per_page": { + "type": "integer", + "description": "Items per page." + }, + "total": { + "type": "integer", + "description": "Total matching items." + }, + "last_page": { + "type": "integer", + "description": "The last page number." + } + }, + "required": [ + "page", + "per_page", + "total", + "last_page" + ] + }, + { + "type": "object", + "title": "CursorPagination", + "properties": { + "per_page": { + "type": "integer", + "description": "Items per page." + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "description": "Cursor for the next page, or null on the last." + }, + "prev_cursor": { + "type": [ + "string", + "null" + ], + "description": "Cursor for the previous page, or null on the first." + } + }, + "required": [ + "per_page", + "next_cursor", + "prev_cursor" + ] + } + ] + }, + "Links": { + "type": "object", + "title": "Links", + "description": "Navigation links. Only self is present on item responses.", + "properties": { + "self": { + "type": "string", + "format": "uri", + "description": "The URL of this response." + }, + "first": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "last": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "next": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "prev": { + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "self" + ] + } + }, + "parameters": { + "XLunarChannel": { + "name": "X-Lunar-Channel", + "in": "header", + "required": false, + "schema": { + "type": "string" + }, + "description": "Handle of the channel to serve. Defaults to the default channel; an unknown handle is rejected with 422." + }, + "XLunarCurrency": { + "name": "X-Lunar-Currency", + "in": "header", + "required": false, + "schema": { + "type": "string" + }, + "description": "ISO 4217 code of the currency to price in. Defaults to the default currency; an unknown or disabled code is rejected with 422." + }, + "XLunarCart": { + "name": "X-Lunar-Cart", + "in": "header", + "required": false, + "schema": { + "type": "string" + }, + "description": "The signed cart token a previous response returned on X-Lunar-Cart. An invalid or expired token is rejected with 401, a token for a cart that no longer exists with 404." + }, + "AcceptLanguage": { + "name": "Accept-Language", + "in": "header", + "required": false, + "schema": { + "type": "string" + }, + "description": "Preferred languages. The best match among the store languages is used for translatable fields; otherwise the default language." + } + }, + "headers": { + "X-Lunar-Channel": { + "schema": { + "type": "string" + }, + "description": "Handle of the channel the response was served for." + }, + "X-Lunar-Currency": { + "schema": { + "type": "string" + }, + "description": "ISO 4217 code of the currency prices are in." + }, + "Content-Language": { + "schema": { + "type": "string" + }, + "description": "The locale translatable fields were resolved in." + }, + "X-Lunar-Cart": { + "schema": { + "type": "string" + }, + "description": "A fresh signed token for the current cart. Present when the request had or created a cart; send it on the next cart request." + } + }, + "responses": { + "NotFound": { + "description": "No resource with that id is visible to the request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "UnprocessableEntity": { + "description": "A query parameter, header or body field is invalid. Each error names its source.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "TooManyRequests": { + "description": "The rate limit was exceeded. Retry after the Retry-After header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } +} diff --git a/specs/0076-rename-collection-fulfilment-to-pickup.md b/specs/0077-rename-collection-fulfilment-to-pickup.md similarity index 98% rename from specs/0076-rename-collection-fulfilment-to-pickup.md rename to specs/0077-rename-collection-fulfilment-to-pickup.md index c4befdc26b..e9b4644e4f 100644 --- a/specs/0076-rename-collection-fulfilment-to-pickup.md +++ b/specs/0077-rename-collection-fulfilment-to-pickup.md @@ -1,9 +1,9 @@ -# 0076 — Rename the collection fulfilment method to pickup +# 0077 — Rename the collection fulfilment method to pickup - Status: accepted - Author: Glenn Jacobs - Created: 2026-09-07 -- TODO item: Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0076) +- TODO item: Rename the collection fulfilment method and states to pickup — Shopify/Woo/Magento-aligned terminology (spec 0077) ## Problem diff --git a/specs/README.md b/specs/README.md index 663f77e03d..8d349ff11f 100644 --- a/specs/README.md +++ b/specs/README.md @@ -94,4 +94,5 @@ Each spec carries a `Status:` line in its frontmatter / header: | 0073 | Split `AmountOff` into `PercentageOff` and `FixedAmountOff` | implemented | | 0074 | Panel global search (command palette) | implemented | | 0075 | First staff account creation without the Filament admin | implemented | -| 0076 | Rename the collection fulfilment method to pickup | accepted | +| 0076 | Filament admin and bridge hardening | implemented | +| 0077 | Rename the collection fulfilment method to pickup | accepted |