openapi: 3.1.0
info:
  title: Harmon Storefront API
  description: >
    The **Harmon Storefront API** is a public, machine-authenticated read +
    commerce

    API for building your own ecommerce site on Harmon.


    * **Authentication** — every call carries a tenant-scoped API key as
      `Authorization: Bearer hk_…`. A **publishable** key (browser-safe) authorizes
      anonymous catalog reads + initiating shopper auth; a **secret** key
      (server-side only) additionally authorizes customer-scoped reads, checkout, and
      webhook management. Customer-scoped routes also require a **shopper token** in
      the `X-Storefront-Shopper-Token` header.
    * **Modes** — a key prefixed `hk_test_` runs in **sandbox** mode; every
    other key
      is **live**. The mode is echoed in the `X-Harmon-Mode` response header.
    * **Caching** — catalog reads return a strong `ETag` + `Cache-Control`; send
      `If-None-Match` to revalidate (`304`).
    * **Errors** - every hard error returns `{ "error": { "code", "message", ...
    } }`;
      branch on the stable `error.code` (`unauthorized` / `forbidden` / `not_found` /
      `conflict` / `validation_error` / `rate_limited` / `bad_gateway` / ...). The
      legacy top-level `detail` is still mirrored for one release.
    * **Money** - every money field is a JSON **number** in the row's ISO-4217
      `currency` (there are no integer cents); an unknown amount is `null`, never
      `0`. Rate fields (`effective_gct_rate`) are fractions (`0.15` == 15%), distinct
      from amount fields (`gct_amount`).
    * **Webhooks** — outbound events are signed `X-Harmon-Signature:
    sha256=<hmac>`;
      verify against your subscription's signing secret.
    * **Rate limits** — each key is limited per minute; over the limit returns
    `429`
      with `Retry-After`.

    See the [merchant integration guide](https://github.com/harmon/docs) for the
    full

    contract, error model, and signature-verification snippets.
  contact:
    name: Harmon Integrations
    url: https://api.harmon.example/
  license:
    name: Proprietary
  version: 1.0.0
servers:
  - url: https://api.harmon.example
    description: Storefront API host
paths:
  /health:
    get:
      tags:
        - health
      summary: Health
      operationId: health_health_get
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthOut"
  /v1/ping:
    get:
      tags:
        - diagnostics
      summary: Ping
      operationId: ping_v1_ping_get
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrincipalOut"
  /v1/products:
    get:
      tags:
        - catalog
      summary: List Products
      description: >-
        The anonymous catalog page (list price + tax + coarse availability).


        **Customer pricing (decision 0.2, ALLOW):** a **publishable** key
        presenting a

        **valid** ``X-Storefront-Shopper-Token`` receives the customer-priced
        catalog -

        the publishable key identifies the integration/tenant and the shopper
        token is

        the customer's own credential (tenant stays key-derived, customer
        token-derived;

        Invariant 3). Such a response is ``Cache-Control: private`` and
        ``Vary``s on the

        shopper-token header so a negotiated price never lands in a
        shared/anonymous

        cache entry. An absent token is the normal anonymous (list-price) path;
        a

        present-but-**invalid** token fails closed (401/403), never a silent
        downgrade.
      operationId: list_products_v1_products_get
      parameters:
        - name: q
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: Q
        - name: category
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: Category
        - name: tag
          in: query
          required: false
          schema:
            anyOf:
              - type: array
                items:
                  type: string
              - type: "null"
            description: Filter to products carrying merchandising tags. Pass the tag `slug`
              from `GET /v1/tags`. Repeatable (`?tag=a&tag=b`); a product must
              carry **all** given tags (AND). Group-agnostic (matches the slug
              in any tag group).
            title: Tag
          description: Filter to products carrying merchandising tags. Pass the tag `slug`
            from `GET /v1/tags`. Repeatable (`?tag=a&tag=b`); a product must
            carry **all** given tags (AND). Group-agnostic (matches the slug in
            any tag group).
        - name: vehicle_id
          in: query
          required: false
          schema:
            anyOf:
              - type: integer
              - type: "null"
            description: "Parts-catalog filter: restrict to products that fit this vehicle.
              Requires the parts-catalog (fitment) feature — ignored for a
              tenant that does not have it (the `fitment_enabled` flag on this
              response reports availability)."
            title: Vehicle Id
          description: "Parts-catalog filter: restrict to products that fit this vehicle.
            Requires the parts-catalog (fitment) feature — ignored for a tenant
            that does not have it (the `fitment_enabled` flag on this response
            reports availability)."
        - name: updated_since
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            description: Reserved for delta paging. Not yet honoured by the catalog
              projection - a value is accepted but does not filter today; do not
              rely on it to page changes (storefront-cleanup plan Phase 5.5).
            title: Updated Since
          description: Reserved for delta paging. Not yet honoured by the catalog
            projection - a value is accepted but does not filter today; do not
            rely on it to page changes (storefront-cleanup plan Phase 5.5).
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 200
            minimum: 1
            default: 50
            title: Limit
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
            title: Offset
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductListOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/products/{sku}:
    get:
      tags:
        - catalog
      summary: Get Product
      operationId: get_product_v1_products__sku__get
      parameters:
        - name: sku
          in: path
          required: true
          schema:
            type: string
            title: Sku
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductDetailOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/categories:
    get:
      tags:
        - catalog
      summary: List Categories
      description: >-
        The tenant category tree. A strong-ETag, ``public`` cacheable GET

        (storefront-cleanup plan Phase 5.3): send ``If-None-Match`` to
        revalidate

        (``304``). The taxonomy is customer-independent, so it is always
        ``public``.
      operationId: list_categories_v1_categories_get
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoryTreeOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/tags:
    get:
      tags:
        - catalog
      summary: List Tags
      description: >-
        The tenant's grouped tag taxonomy - the faceted counterpart to

        ``GET /v1/categories`` (storefront-cleanup plan Phase 5.1). Sourced from

        inventory; a strong-ETag, ``public`` cacheable GET consistent with
        categories

        (Phase 5.3).
      operationId: list_tags_v1_tags_get
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagGroupsOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/products/{sku}/availability:
    get:
      tags:
        - availability
      summary: Product Availability
      description: >-
        One SKU's coarse availability band. A strong-ETag, ``public`` cacheable
        GET

        (storefront-cleanup plan Phase 5.3): the band is customer-independent,
        so send

        ``If-None-Match`` to revalidate (``304``). The multi-SKU batch is a POST
        and is

        not cacheable.
      operationId: product_availability_v1_products__sku__availability_get
      parameters:
        - name: sku
          in: path
          required: true
          schema:
            type: string
            title: Sku
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AvailabilityOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/availability/batch:
    post:
      tags:
        - availability
      summary: Availability Batch
      operationId: availability_batch_v1_availability_batch_post
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AvailabilityBatchIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AvailabilityBatchOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/me:
    get:
      tags:
        - customer
      summary: Get Me
      operationId: get_me_v1_me_get
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/me/credit-status:
    get:
      tags:
        - customer
      summary: Get Credit Status
      operationId: get_credit_status_v1_me_credit_status_get
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditStatusOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts:
    post:
      tags:
        - cart
      summary: Create Cart
      operationId: create_cart_v1_carts_post
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "201":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts/{cart_id}:
    get:
      tags:
        - cart
      summary: Get Cart
      operationId: get_cart_v1_carts__cart_id__get
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts/{cart_id}/lines:
    post:
      tags:
        - cart
      summary: Add Line
      operationId: add_line_v1_carts__cart_id__lines_post
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CartLineIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts/{cart_id}/lines/{line_id}:
    patch:
      tags:
        - cart
      summary: Update Line
      operationId: update_line_v1_carts__cart_id__lines__line_id__patch
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: line_id
          in: path
          required: true
          schema:
            type: string
            title: Line Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CartLineUpdateIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
    delete:
      tags:
        - cart
      summary: Remove Line
      operationId: remove_line_v1_carts__cart_id__lines__line_id__delete
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: line_id
          in: path
          required: true
          schema:
            type: string
            title: Line Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts/{cart_id}/price:
    post:
      tags:
        - cart
      summary: Price Cart
      operationId: price_cart_v1_carts__cart_id__price_post
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartPricedOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/carts/{cart_id}/promotions:
    post:
      tags:
        - cart
      summary: Apply Promotion
      operationId: apply_promotion_v1_carts__cart_id__promotions_post
      parameters:
        - name: cart_id
          in: path
          required: true
          schema:
            type: string
            title: Cart Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PromoCodeIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CartOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/checkout/shipping-options:
    get:
      tags:
        - checkout
      summary: Shipping Options
      description: >-
        The tenant's enabled shipping options for the checkout step (courier-

        fulfilment plan Phase 4.2). Empty (and ``couriers_enabled=False``) for a

        tenant that never opted in, so the storefront renders no shipping step
        and

        requires no carrier selection at placement (Invariant 8).
      operationId: shipping_options_v1_checkout_shipping_options_get
      parameters:
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShippingOptionsOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/checkout:
    post:
      tags:
        - checkout
      summary: Checkout
      operationId: checkout_v1_checkout_post
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: Idempotency-Key
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckoutIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutResultOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/checkout/{order_id}/repay:
    post:
      tags:
        - checkout
      summary: Repay
      description: >-
        Re-open payment for a still-held order whose session expired/failed/was

        cancelled (storefront-cleanup plan Phase 4.1).


        Looks up the order's persisted payment-session link scoped to the
        token's

        tenant + customer (an unknown / cross-customer order 404s, no existence
        leak,

        Invariant 3). A still-held order (``PENDING_PAYMENT``) whose session is
        dead

        gets a fresh session + gateway redirect; a paid/confirmed order does not

        re-open one (409). When a fresh session is minted the link is re-pointed
        to it

        so ``payment-status`` polling follows the live session.
      operationId: repay_v1_checkout__order_id__repay_post
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            title: Order Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutResultOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/checkout/{order_id}/payment-status:
    get:
      tags:
        - checkout
      summary: Payment Status
      description: >-
        Poll the payment session opened for a prepaid order (plan Phase 5.2).


        A thin proxy of the payments-service session status the shopper SPA
        polls

        after the gateway return. The order → session link is looked up scoped
        to the

        token's tenant + customer, so an unknown order — or one belonging to
        another

        customer — 404s with no existence leak (Invariant 8). The webhook
        remains the

        source of truth; this read never mutates ledger state (Invariant 3).
      operationId: payment_status_v1_checkout__order_id__payment_status_get
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            title: Order Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutPaymentStatusOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/orders:
    get:
      tags:
        - orders
      summary: List Orders
      operationId: list_orders_v1_orders_get
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 200
            minimum: 1
            default: 50
            title: Limit
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
            title: Offset
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StorefrontOrdersOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/orders/{order_id}:
    get:
      tags:
        - orders
      summary: Get Order
      operationId: get_order_v1_orders__order_id__get
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            title: Order Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StorefrontOrderDetailOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/orders/{order_id}/cancel:
    post:
      tags:
        - orders
      summary: Cancel
      operationId: cancel_v1_orders__order_id__cancel_post
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            title: Order Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderCancelIn"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StorefrontOrderDetailOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/invoices:
    get:
      tags:
        - invoices
      summary: List Invoices
      operationId: list_invoices_v1_invoices_get
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 200
            minimum: 1
            default: 50
            title: Limit
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
            title: Offset
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoicesOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/invoices/{invoice_id}:
    get:
      tags:
        - invoices
      summary: Get Invoice
      operationId: get_invoice_v1_invoices__invoice_id__get
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema:
            type: string
            title: Invoice Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoiceDetailOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/invoices/{invoice_id}/pay:
    post:
      tags:
        - invoices
      summary: Pay Invoice
      description: >-
        Open a gateway payment session for one of the customer's own open
        invoices

        (storefront-cleanup plan Phase 4.2). A paid/voided invoice 409s; a
        missing /

        cross-customer invoice 404s (OMS scope, no existence leak); a tenant
        without

        online payments enabled 403s (payments-service gate).
      operationId: pay_invoice_v1_invoices__invoice_id__pay_post
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema:
            type: string
            title: Invoice Id
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoicePaymentSessionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/invoices/{invoice_id}/pdf:
    get:
      tags:
        - invoices
      summary: Get Invoice Pdf
      description: >-
        Stream the customer's own invoice as a PDF, rendered on demand

        (document-generation plan Phase 3.4).


        Composes the fully-resolved payload from the customer-scoped
        invoice/order

        read + tenant branding, POSTs it to the stateless documents service, and

        streams back ``application/pdf``. ``?download=1`` switches the
        disposition to

        ``attachment``; the default ``inline`` opens it in a browser tab. A
        missing

        or cross-customer invoice 404s (OMS scope) and never renders.
      operationId: get_invoice_pdf_v1_invoices__invoice_id__pdf_get
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema:
            type: string
            title: Invoice Id
        - name: download
          in: query
          required: false
          schema:
            type: boolean
            default: false
            title: Download
        - name: X-Storefront-Shopper-Token
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: "null"
            title: X-Storefront-Shopper-Token
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema: {}
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/webhooks:
    get:
      tags:
        - webhooks
      summary: List Subscriptions
      operationId: list_subscriptions_v1_webhooks_get
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                items:
                  $ref: "#/components/schemas/WebhookSubscriptionOut"
                type: array
                title: Response List Subscriptions V1 Webhooks Get
    post:
      tags:
        - webhooks
      summary: Create Subscription
      operationId: create_subscription_v1_webhooks_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSubscriptionCreate"
        required: true
      responses:
        "201":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/webhooks/{subscription_id}:
    get:
      tags:
        - webhooks
      summary: Get Subscription
      operationId: get_subscription_v1_webhooks__subscription_id__get
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            title: Subscription Id
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
    patch:
      tags:
        - webhooks
      summary: Update Subscription
      description: >-
        Patch a subscription's endpoint / events / status (storefront-cleanup
        plan

        Phase 6.5 - brings the public merchant surface to parity with the staff

        ``/internal/admin/webhooks`` PATCH). 404 when it is not the key's
        tenant; the

        read response carries no secret (rotate to mint a new one).
      operationId: update_subscription_v1_webhooks__subscription_id__patch
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            title: Subscription Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSubscriptionUpdate"
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/webhooks/{subscription_id}/rotate-secret:
    post:
      tags:
        - webhooks
      summary: Rotate Subscription Secret
      description: >-
        Mint a fresh reveal-once ``signing_secret`` for a subscription

        (storefront-cleanup plan Phase 6.5; the schema already hinted rotation).
        The

        new secret is returned **once** here and never re-read; the old one
        stops

        verifying immediately. 404 cross-tenant (no existence leak).
      operationId: rotate_subscription_secret_v1_webhooks__subscription_id__rotate_secret_post
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            title: Subscription Id
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/webhooks/{subscription_id}/disable:
    post:
      tags:
        - webhooks
      summary: Disable Subscription
      operationId: disable_subscription_v1_webhooks__subscription_id__disable_post
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            title: Subscription Id
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
  /v1/webhooks/{subscription_id}/ping:
    post:
      tags:
        - webhooks
      summary: Ping Subscription
      operationId: ping_subscription_v1_webhooks__subscription_id__ping_post
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            title: Subscription Id
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookPingOut"
        "422":
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HTTPValidationError"
components:
  schemas:
    AvailabilityBatchIn:
      properties:
        skus:
          items:
            type: string
          type: array
          title: Skus
      type: object
      required:
        - skus
      title: AvailabilityBatchIn
      description: "``POST /v1/availability/batch`` request body."
    AvailabilityBatchOut:
      properties:
        items:
          items:
            $ref: "#/components/schemas/AvailabilityOut"
          type: array
          title: Items
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: AvailabilityBatchOut
      description: "``POST /v1/availability/batch`` — a band per requested SKU."
    AvailabilityOut:
      properties:
        sku:
          type: string
          title: Sku
        availability:
          type: string
          title: Availability
          default: UNKNOWN
      type: object
      required:
        - sku
      title: AvailabilityOut
      description: "``GET /v1/products/{sku}/availability`` — a coarse band, never a count."
    CapabilitiesOut:
      properties:
        permissions:
          items:
            type: string
          type: array
          title: Permissions
          default: []
        can_order:
          type: boolean
          title: Can Order
          default: false
        can_view_ar:
          type: boolean
          title: Can View Ar
          default: false
      type: object
      title: CapabilitiesOut
      description: |-
        Affordance flags projected from the shopper token's ``permissions[]`` —
        the SPA asks ``can_order`` rather than testing a raw permission string.
    CartLineIn:
      properties:
        sku:
          type: string
          maxLength: 192
          minLength: 1
          title: Sku
        quantity:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: "null"
          title: Quantity
        order_uom:
          anyOf:
            - type: string
              maxLength: 32
            - type: "null"
          title: Order Uom
        order_quantity:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: "null"
          title: Order Quantity
      type: object
      required:
        - sku
      title: CartLineIn
      description: >-
        ``POST /v1/carts/{id}/lines`` — UOM-aware. Exactly one quantity form:

        the base ``quantity`` *or* the selling-unit ``order_uom`` +
        ``order_quantity``

        (OMS converts at checkout). Mirrors the OMS ``OrderLineIn`` one-form
        rule.
    CartLineOut:
      properties:
        line_id:
          type: string
          title: Line Id
        sku:
          type: string
          title: Sku
        quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Quantity
        order_uom:
          anyOf:
            - type: string
            - type: "null"
          title: Order Uom
        order_quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Order Quantity
      type: object
      required:
        - line_id
        - sku
      title: CartLineOut
      description: >-
        One line of a persisted cart. ``line_id`` is **always present** here (it
        is

        storefront-api's own operational state), unlike
        ``StorefrontOrderLineOut.

        line_id`` which mirrors OMS and is nullable (storefront-cleanup plan
        Phase

        6.1 - the required-vs-optional split is intentional and documented).
    CartLineUpdateIn:
      properties:
        quantity:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: "null"
          title: Quantity
        order_quantity:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: "null"
          title: Order Quantity
      type: object
      title: CartLineUpdateIn
      description: |-
        ``PATCH /v1/carts/{id}/lines/{line_id}`` — adjust the quantity of the
        form the line already carries (base ``quantity`` or ``order_quantity``).
    CartOut:
      properties:
        cart_id:
          type: string
          title: Cart Id
        status:
          type: string
          title: Status
          default: OPEN
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        promo_code:
          anyOf:
            - type: string
            - type: "null"
          title: Promo Code
        lines:
          items:
            $ref: "#/components/schemas/CartLineOut"
          type: array
          title: Lines
          default: []
      type: object
      required:
        - cart_id
      title: CartOut
      description: "``POST /v1/carts`` + cart-mutation responses — the persisted cart."
    CartPricedLineOut:
      properties:
        line_id:
          type: string
          title: Line Id
        sku:
          type: string
          title: Sku
        description:
          anyOf:
            - type: string
            - type: "null"
          title: Description
        quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Quantity
        order_uom:
          anyOf:
            - type: string
            - type: "null"
          title: Order Uom
        order_quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Order Quantity
        unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Unit Price
        estimated_line_total:
          anyOf:
            - type: number
            - type: "null"
          title: Estimated Line Total
        availability:
          type: string
          title: Availability
          default: UNKNOWN
        sellable:
          type: boolean
          title: Sellable
          default: false
      type: object
      required:
        - line_id
        - sku
      title: CartPricedLineOut
    CartPricedOut:
      properties:
        cart_id:
          type: string
          title: Cart Id
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        lines:
          items:
            $ref: "#/components/schemas/CartPricedLineOut"
          type: array
          title: Lines
          default: []
        estimated_subtotal:
          anyOf:
            - type: number
            - type: "null"
          title: Estimated Subtotal
        estimated_tax:
          anyOf:
            - type: number
            - type: "null"
          title: Estimated Tax
        estimated_total:
          anyOf:
            - type: number
            - type: "null"
          title: Estimated Total
        promo_code:
          anyOf:
            - type: string
            - type: "null"
          title: Promo Code
        available_credit:
          anyOf:
            - type: number
            - type: "null"
          title: Available Credit
        within_credit:
          anyOf:
            - type: boolean
            - type: "null"
          title: Within Credit
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - cart_id
      title: CartPricedOut
      description: >-
        ``POST /v1/carts/{id}/price`` — a **display-only** estimate.


        The subtotal/tax/total are computed from OMS' customer-priced catalog
        and are

        advisory; the binding totals come from OMS at checkout (Invariant 5 — no

        second pricing engine). A selling-unit line cannot be estimated here
        (the

        unit→base factor is OMS-only), so a cart with any such line collapses
        the

        subtotal to null rather than under-counting.
    CategoryNodeOut:
      properties:
        id:
          type: string
          title: Id
        parent_id:
          anyOf:
            - type: string
            - type: "null"
          title: Parent Id
        name:
          type: string
          title: Name
        slug:
          anyOf:
            - type: string
            - type: "null"
          title: Slug
        children:
          items:
            $ref: "#/components/schemas/CategoryNodeOut"
          type: array
          title: Children
          default: []
      type: object
      required:
        - id
        - name
      title: CategoryNodeOut
      description: One node of the category tree (recursive).
    CategoryTreeOut:
      properties:
        tree:
          items:
            $ref: "#/components/schemas/CategoryNodeOut"
          type: array
          title: Tree
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: CategoryTreeOut
      description: "``GET /v1/categories`` — the tenant category tree."
    CheckoutCreditOut:
      properties:
        credit_limit:
          anyOf:
            - type: number
            - type: "null"
          title: Credit Limit
        current_balance:
          anyOf:
            - type: number
            - type: "null"
          title: Current Balance
        available_credit:
          anyOf:
            - type: number
            - type: "null"
          title: Available Credit
        order_total:
          anyOf:
            - type: number
            - type: "null"
          title: Order Total
        shortfall:
          anyOf:
            - type: number
            - type: "null"
          title: Shortfall
      type: object
      title: CheckoutCreditOut
      description: The over-credit detail surfaced as a structured state (not a raw 409).
    CheckoutIn:
      properties:
        cart_id:
          type: string
          title: Cart Id
        notes:
          anyOf:
            - type: string
            - type: "null"
          title: Notes
        shipping:
          anyOf:
            - $ref: "#/components/schemas/CheckoutShippingIn"
            - type: "null"
      type: object
      required:
        - cart_id
      title: CheckoutIn
      description: >-
        ``POST /v1/checkout`` — turn a persisted cart into a confirmed order.


        The ``customer_id`` and ``channel`` are NOT client-supplied: the
        customer

        comes from the shopper token and the channel is forced to ``ECOMMERCE``

        server-side (Invariants 1 & 3). Idempotency rides the
        ``Idempotency-Key``

        header, forwarded as the OMS draft's ``idempotency_key``. ``shipping``
        is the

        carrier selection — required only for tenants that have enabled couriers

        (courier-fulfilment plan Phase 4.2).
    CheckoutPaymentStatusOut:
      properties:
        order_id:
          type: string
          title: Order Id
        payment_session_id:
          type: string
          title: Payment Session Id
        status:
          type: string
          title: Status
        redirect_url:
          anyOf:
            - type: string
            - type: "null"
          title: Redirect Url
      type: object
      required:
        - order_id
        - payment_session_id
        - status
      title: CheckoutPaymentStatusOut
      description: >-
        ``GET /v1/checkout/{order_id}/payment-status`` — the current status of
        the

        payment session opened for a prepaid order (payment-gateway plan Phase
        5.2).


        A thin proxy of the payments-service session status the shopper SPA
        polls

        after the gateway return. The webhook remains the source of truth; this
        read

        is advisory (``PENDING`` until the capture lands).
    CheckoutResultOut:
      properties:
        state:
          type: string
          title: State
        order_id:
          anyOf:
            - type: string
            - type: "null"
          title: Order Id
        status:
          anyOf:
            - type: string
            - type: "null"
          title: Status
        credit:
          anyOf:
            - $ref: "#/components/schemas/CheckoutCreditOut"
            - type: "null"
        compensation:
          anyOf:
            - type: string
            - type: "null"
          title: Compensation
        redirect_url:
          anyOf:
            - type: string
            - type: "null"
          title: Redirect Url
        payment_session_id:
          anyOf:
            - type: string
            - type: "null"
          title: Payment Session Id
        invoice_id:
          anyOf:
            - type: string
            - type: "null"
          title: Invoice Id
        shipment_id:
          anyOf:
            - type: string
            - type: "null"
          title: Shipment Id
      type: object
      required:
        - state
      title: CheckoutResultOut
      description: >-
        ``POST /v1/checkout`` result.


        ``state`` is one of:


        * ``confirmed`` — the order is on its way to fulfilment (credit / COD
        terms,
          or a pay-online tenant's credit customer);
        * ``payment_required`` — a prepaid ECOMMERCE order on a pay-online
        tenant:
          the order is confirmed but **held** until paid, and ``redirect_url`` sends
          the shopper to the gateway's hosted page. ``payment_session_id`` +
          ``invoice_id`` identify the opened session (payment-gateway plan Phase 5.1);
        * ``over_credit_limit`` — carries the credit shortfall + the
        compensation
          outcome (the stranded draft is cancelled).
    CheckoutShippingIn:
      properties:
        courier_id:
          type: string
          title: Courier Id
        pickup_location_id:
          anyOf:
            - type: string
            - type: "null"
          title: Pickup Location Id
        field_values:
          type: object
          title: Field Values
      type: object
      required:
        - courier_id
      title: CheckoutShippingIn
      description: >-
        The shopper's carrier selection, required at ``POST /v1/checkout`` when
        the

        tenant has enabled couriers. ``pickup_location_id`` is required iff the
        chosen

        courier exposes pickup locations (validated by the courier service);

        ``field_values`` carries the courier's ``customer_at_checkout`` fields.
    CreditStatusOut:
      properties:
        customer_id:
          type: string
          title: Customer Id
        credit_limit:
          anyOf:
            - type: number
            - type: "null"
          title: Credit Limit
        current_balance:
          anyOf:
            - type: number
            - type: "null"
          title: Current Balance
        available_credit:
          anyOf:
            - type: number
            - type: "null"
          title: Available Credit
        payment_terms:
          anyOf:
            - type: string
            - type: "null"
          title: Payment Terms
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        can_place_order:
          type: boolean
          title: Can Place Order
          default: false
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - customer_id
      title: CreditStatusOut
      description: "``GET /v1/me/credit-status`` — the shopper's AR standing (self only)."
    DownstreamErrorOut:
      properties:
        field:
          type: string
          title: Field
        code:
          type: string
          title: Code
        detail:
          anyOf:
            - type: string
            - type: "null"
          title: Detail
      type: object
      required:
        - field
        - code
      title: DownstreamErrorOut
      description: >-
        One degraded section on a partial fan-out response (mirrors the BFFs).


        ``field`` names the section that failed; ``code`` is a stable machine
        label

        (``downstream_timeout`` / ``downstream_unavailable`` / …); ``detail`` is
        a

        human hint.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: "#/components/schemas/ValidationError"
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    HealthOut:
      properties:
        service:
          type: string
          title: Service
          default: storefront-api
        status:
          type: string
          title: Status
        db:
          type: string
          title: Db
        redis:
          type: string
          title: Redis
      type: object
      required:
        - status
        - db
        - redis
      title: HealthOut
      description: "``GET /health`` — liveness plus a coarse dependency probe."
    InvoiceDetailOut:
      properties:
        invoice_id:
          type: string
          title: Invoice Id
        order_id:
          anyOf:
            - type: string
            - type: "null"
          title: Order Id
        invoice_number:
          anyOf:
            - type: string
            - type: "null"
          title: Invoice Number
        status:
          type: string
          title: Status
        total:
          anyOf:
            - type: number
            - type: "null"
          title: Total
        amount_paid:
          anyOf:
            - type: number
            - type: "null"
          title: Amount Paid
        amount_due:
          anyOf:
            - type: number
            - type: "null"
          title: Amount Due
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        issued_at:
          anyOf:
            - type: string
            - type: "null"
          title: Issued At
        due_date:
          anyOf:
            - type: string
            - type: "null"
          title: Due Date
        pdf_url:
          anyOf:
            - type: string
            - type: "null"
          title: Pdf Url
        payable_online:
          type: boolean
          title: Payable Online
          default: false
        payments:
          items:
            $ref: "#/components/schemas/InvoicePaymentOut"
          type: array
          title: Payments
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - invoice_id
        - status
      title: InvoiceDetailOut
      description: >-
        ``GET /v1/invoices/{id}`` — invoice + recorded payments + PDF link.


        Extends ``InvoiceRowOut`` (storefront-cleanup plan Phase 6.4): the
        list-row

        fields are shared verbatim - the detail is the row plus recorded
        payments, the

        ``payable_online`` hint, and the degradation ``errors[]``. Inheriting
        keeps the

        wire byte-identical (the shared fields serialize first, in the same
        order).
    InvoicePaymentOut:
      properties:
        payment_id:
          anyOf:
            - type: string
            - type: "null"
          title: Payment Id
        amount:
          anyOf:
            - type: number
            - type: "null"
          title: Amount
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        method:
          anyOf:
            - type: string
            - type: "null"
          title: Method
        reference:
          anyOf:
            - type: string
            - type: "null"
          title: Reference
        received_at:
          anyOf:
            - type: string
            - type: "null"
          title: Received At
      type: object
      title: InvoicePaymentOut
    InvoicePaymentSessionOut:
      properties:
        invoice_id:
          type: string
          title: Invoice Id
        payment_session_id:
          anyOf:
            - type: string
            - type: "null"
          title: Payment Session Id
        redirect_url:
          anyOf:
            - type: string
            - type: "null"
          title: Redirect Url
        status:
          type: string
          title: Status
      type: object
      required:
        - invoice_id
        - status
      title: InvoicePaymentSessionOut
      description: >-
        ``POST /v1/invoices/{invoice_id}/pay`` result (storefront-cleanup plan
        Phase

        4.2) - the payments-service session opened for an already-open ECOMMERCE

        invoice.


        The amount/currency are bound server-side from the OMS invoice by

        payments-service, never the client (Invariant 4). ``redirect_url`` sends
        the

        shopper to the gateway's hosted page; ``status`` is the freshly-created

        session's status. A paid/voided invoice never reaches here (409); a

        missing/cross-customer invoice 404s.
    InvoiceRowOut:
      properties:
        invoice_id:
          type: string
          title: Invoice Id
        order_id:
          anyOf:
            - type: string
            - type: "null"
          title: Order Id
        invoice_number:
          anyOf:
            - type: string
            - type: "null"
          title: Invoice Number
        status:
          type: string
          title: Status
        total:
          anyOf:
            - type: number
            - type: "null"
          title: Total
        amount_paid:
          anyOf:
            - type: number
            - type: "null"
          title: Amount Paid
        amount_due:
          anyOf:
            - type: number
            - type: "null"
          title: Amount Due
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        issued_at:
          anyOf:
            - type: string
            - type: "null"
          title: Issued At
        due_date:
          anyOf:
            - type: string
            - type: "null"
          title: Due Date
        pdf_url:
          anyOf:
            - type: string
            - type: "null"
          title: Pdf Url
      type: object
      required:
        - invoice_id
        - status
      title: InvoiceRowOut
    InvoicesOut:
      properties:
        invoices:
          items:
            $ref: "#/components/schemas/InvoiceRowOut"
          type: array
          title: Invoices
          default: []
        total:
          type: integer
          title: Total
          default: 0
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: InvoicesOut
      description: "``GET /v1/invoices`` — the shopper's own invoices (OMS auto-scopes)."
    MeOut:
      properties:
        tenant_id:
          type: string
          title: Tenant Id
        customer_id:
          type: string
          title: Customer Id
        customer_name:
          anyOf:
            - type: string
            - type: "null"
          title: Customer Name
        email:
          anyOf:
            - type: string
            - type: "null"
          title: Email
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        payment_terms:
          anyOf:
            - type: string
            - type: "null"
          title: Payment Terms
        user_timezone:
          anyOf:
            - type: string
            - type: "null"
          title: User Timezone
        tenant_timezone:
          anyOf:
            - type: string
            - type: "null"
          title: Tenant Timezone
        effective_timezone:
          anyOf:
            - type: string
            - type: "null"
          title: Effective Timezone
        capabilities:
          allOf:
            - $ref: "#/components/schemas/CapabilitiesOut"
          default:
            permissions: []
            can_order: false
            can_view_ar: false
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - tenant_id
        - customer_id
      title: MeOut
      description: >-
        ``GET /v1/me`` — shopper session bootstrap.


        The scoping axes (``tenant_id`` + ``customer_id``) and capabilities come

        straight off the verified token (never the client). The display name +

        currency + timezones are best-effort enriched from OMS / auth and
        degrade to

        nulls rather than 5xx-ing the first paint.
    MediaRefOut:
      properties:
        kind:
          type: string
          title: Kind
          default: IMAGE
        role:
          type: string
          title: Role
          default: gallery
        position:
          type: integer
          title: Position
          default: 0
        alt_text:
          anyOf:
            - type: string
            - type: "null"
          title: Alt Text
        thumb_url:
          anyOf:
            - type: string
            - type: "null"
          title: Thumb Url
        large_url:
          anyOf:
            - type: string
            - type: "null"
          title: Large Url
        provider:
          anyOf:
            - type: string
            - type: "null"
          title: Provider
        embed_url:
          anyOf:
            - type: string
            - type: "null"
          title: Embed Url
        poster_url:
          anyOf:
            - type: string
            - type: "null"
          title: Poster Url
        duration_seconds:
          anyOf:
            - type: integer
            - type: "null"
          title: Duration Seconds
      type: object
      title: MediaRefOut
      description: >-
        One gallery entry on a product's public gallery (product-media plan
        Phase

        6.1; rich-pdp plan Phase 8.2 adds video embeds).


        Mirrored from the OMS shadow's denormalized per-SKU gallery, itself

        snapshotted by inventory at attach/READY time (Invariant 5): the
        resolved

        public derivative / embed URLs are carried inline, so a merchant's
        storefront

        renders the gallery without ever calling the media service or a video

        provider. The internal ``media_asset_id`` is deliberately **not**
        exposed on

        the public wire — only the derivative URLs and presentation metadata.

        ``role`` is ``"cover"`` for the single resolved cover and ``"gallery"``
        for

        the rest; the list arrives cover-first.


        ``kind`` is ``"IMAGE"`` (the WebP derivative carried on ``thumb_url`` /

        ``large_url``) or ``"VIDEO"`` (a YouTube/Vimeo embed carrying
        ``provider`` /

        ``embed_url`` / ``poster_url`` / ``duration_seconds``; a video is never
        the

        cover, Invariant carried from product-media). The video fields are
        ``None``

        on an image entry, and an image's WebP URLs are ``None`` on a video
        entry.
    OrderCancelIn:
      properties:
        reason:
          anyOf:
            - type: string
              maxLength: 255
            - type: "null"
          title: Reason
      type: object
      title: OrderCancelIn
      description: "``POST /v1/orders/{id}/cancel`` — an optional shopper-supplied reason."
    OrderDeliveryEventOut:
      properties:
        status:
          type: string
          title: Status
        at:
          anyOf:
            - type: string
            - type: "null"
          title: At
        occurred_at:
          anyOf:
            - type: string
            - type: "null"
          title: Occurred At
      type: object
      required:
        - status
      title: OrderDeliveryEventOut
      description: >-
        One coarse step in the physical-delivery timeline (storefront-cleanup
        plan

        Phase 3.1). Canonical courier status + timestamp only, mirrored from the

        courier tracking read - never a raw courier word, an internal note, or
        the

        event source (courier Invariant 6).
    OrderDeliveryOut:
      properties:
        status:
          type: string
          title: Status
        pickup_location_name:
          anyOf:
            - type: string
            - type: "null"
          title: Pickup Location Name
        pickup_location_address:
          anyOf:
            - type: string
            - type: "null"
          title: Pickup Location Address
        created_at:
          anyOf:
            - type: string
            - type: "null"
          title: Created At
        dropped_off_at:
          anyOf:
            - type: string
            - type: "null"
          title: Dropped Off At
        collected_at:
          anyOf:
            - type: string
            - type: "null"
          title: Collected At
        history:
          items:
            $ref: "#/components/schemas/OrderDeliveryEventOut"
          type: array
          title: History
          default: []
      type: object
      required:
        - status
      title: OrderDeliveryOut
      description: >-
        The physical-shipment lifecycle for an order (storefront-cleanup plan
        Phase

        3.1 / decision 0.5), composed from the courier ``GET
        /orders/{id}/tracking``

        read. Independent of the commercial ``order_status`` - a client must not
        infer

        one lifecycle from the other. ``status`` is the courier
        ``CanonicalStatus``

        (lowercase: ``pending`` / ``dropped_off`` / ``in_transit`` /

        ``arrived_at_pickup`` / ``collected`` / ``cancelled`` / ``exception``).


        This whole block is ``null`` when the tenant has no shipment for the
        order or

        the courier read degrades (Invariant 5 - a courier outage never 5xxs the
        order

        read; the degradation lands in ``StorefrontOrderDetailOut.errors``).
    OrderStatusEventOut:
      properties:
        status:
          type: string
          title: Status
        at:
          anyOf:
            - type: string
            - type: "null"
          title: At
        occurred_at:
          anyOf:
            - type: string
            - type: "null"
          title: Occurred At
        note:
          anyOf:
            - type: string
            - type: "null"
          title: Note
      type: object
      required:
        - status
      title: OrderStatusEventOut
    PrincipalOut:
      properties:
        tenant_id:
          type: string
          title: Tenant Id
        key_kind:
          type: string
          title: Key Kind
        scopes:
          items:
            type: string
          type: array
          title: Scopes
        mode:
          type: string
          title: Mode
          default: live
      type: object
      required:
        - tenant_id
        - key_kind
        - scopes
      title: PrincipalOut
      description: >-
        ``GET /v1/ping`` — the resolved API-key principal.


        The diagnostic endpoint that proves the key edge (Phase 4.2): a caller
        can

        confirm which tenant + scopes its key resolved to without exercising any

        downstream. It echoes only key-derived state — never anything client-

        supplied — so it doubles as the canonical "is my key wired correctly?"

        smoke check in the merchant docs.


        Built explicitly from ``request.state`` in the ping route (never from an
        ORM

        row), so it carries no ``from_attributes`` config.
    ProductDetailOut:
      properties:
        sku:
          type: string
          title: Sku
        base_product_code:
          anyOf:
            - type: string
            - type: "null"
          title: Base Product Code
        description:
          anyOf:
            - type: string
            - type: "null"
          title: Description
        body:
          anyOf:
            - type: string
            - type: "null"
          title: Body
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        is_taxable:
          anyOf:
            - type: boolean
            - type: "null"
          title: Is Taxable
        effective_gct_rate:
          anyOf:
            - type: number
            - type: "null"
          title: Effective Gct Rate
        list_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: List Unit Price
        customer_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Customer Unit Price
        has_customer_rate:
          type: boolean
          title: Has Customer Rate
          default: false
        stock_uom:
          type: string
          title: Stock Uom
          default: EA
        stock_precision:
          type: integer
          title: Stock Precision
          default: 0
        default_sell_uom:
          anyOf:
            - type: string
            - type: "null"
          title: Default Sell Uom
        uom_options:
          items:
            $ref: "#/components/schemas/UomOptionOut"
          type: array
          title: Uom Options
          default: []
        availability:
          type: string
          title: Availability
          default: UNKNOWN
        short_description:
          anyOf:
            - type: string
            - type: "null"
          title: Short Description
        specs:
          type: object
          title: Specs
        badges:
          items:
            type: string
          type: array
          title: Badges
          default: []
        compare_at_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Compare At Unit Price
        discount_percent:
          anyOf:
            - type: integer
            - type: "null"
          title: Discount Percent
        attributes:
          type: object
          title: Attributes
        variants:
          items:
            $ref: "#/components/schemas/ProductVariantOut"
          type: array
          title: Variants
          default: []
        axes:
          items:
            $ref: "#/components/schemas/ProductVariantAxisOut"
          type: array
          title: Axes
          default: []
        fitments:
          items:
            $ref: "#/components/schemas/ProductFitmentOut"
          type: array
          title: Fitments
          description: "Parts-catalog only: the vehicles this product fits. Empty unless
            the tenant has the parts-catalog (fitment) feature."
        cover:
          anyOf:
            - $ref: "#/components/schemas/MediaRefOut"
            - type: "null"
        media:
          items:
            $ref: "#/components/schemas/MediaRefOut"
          type: array
          title: Media
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - sku
      title: ProductDetailOut
      description: |-
        ``GET /v1/products/{sku}``: list price, tax, UOM options, variants,
        fitment.
    ProductFitmentOut:
      properties:
        vehicle_id:
          type: integer
          title: Vehicle Id
        position:
          anyOf:
            - type: string
            - type: "null"
          title: Position
        vehicle_label:
          anyOf:
            - type: string
            - type: "null"
          title: Vehicle Label
        notes:
          anyOf:
            - type: string
            - type: "null"
          title: Notes
      type: object
      required:
        - vehicle_id
      title: ProductFitmentOut
      description: One vehicle a product fits (snapshot label from the inventory link).
    ProductListOut:
      properties:
        items:
          items:
            $ref: "#/components/schemas/ProductSummaryOut"
          type: array
          title: Items
          default: []
        total:
          type: integer
          title: Total
          default: 0
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        fitment_enabled:
          type: boolean
          title: Fitment Enabled
          description: Whether the parts-catalog (fitment) feature is enabled for this
            tenant. When false, the `vehicle_id` filter is ignored and the
            dedicated vehicle/fitment routes return 404.
          default: false
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: ProductListOut
      description: "``GET /v1/products`` — a paginated, filterable catalog page."
    ProductSummaryOut:
      properties:
        sku:
          type: string
          title: Sku
        base_product_code:
          anyOf:
            - type: string
            - type: "null"
          title: Base Product Code
        description:
          anyOf:
            - type: string
            - type: "null"
          title: Description
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        is_taxable:
          anyOf:
            - type: boolean
            - type: "null"
          title: Is Taxable
        effective_gct_rate:
          anyOf:
            - type: number
            - type: "null"
          title: Effective Gct Rate
        list_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: List Unit Price
        customer_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Customer Unit Price
        has_customer_rate:
          type: boolean
          title: Has Customer Rate
          default: false
        stock_uom:
          type: string
          title: Stock Uom
          default: EA
        stock_precision:
          type: integer
          title: Stock Precision
          default: 0
        default_sell_uom:
          anyOf:
            - type: string
            - type: "null"
          title: Default Sell Uom
        availability:
          type: string
          title: Availability
          default: UNKNOWN
        short_description:
          anyOf:
            - type: string
            - type: "null"
          title: Short Description
        badges:
          items:
            type: string
          type: array
          title: Badges
          default: []
        compare_at_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Compare At Unit Price
        discount_percent:
          anyOf:
            - type: integer
            - type: "null"
          title: Discount Percent
        cover:
          anyOf:
            - $ref: "#/components/schemas/MediaRefOut"
            - type: "null"
        media:
          items:
            $ref: "#/components/schemas/MediaRefOut"
          type: array
          title: Media
          default: []
      type: object
      required:
        - sku
      title: ProductSummaryOut
      description: >-
        One row of the product list (``GET /v1/products``).


        ``list_unit_price`` is always the tenant list price.
        ``customer_unit_price``

        + ``has_customer_rate`` are populated **only** when a valid shopper
        token

        rides alongside the key (Phase 6.1) — the anonymous path leaves them
        null /

        false and a client renders the list price (Invariant 5: one pricing
        engine,

        the customer overlay is OMS' ``B2B_RATE``).


        ``cover`` is the single resolved cover image (``null`` for a media-less

        product, Invariant 8) and ``media`` carries the full per-SKU gallery

        (cover-first); both are mirrored from the OMS shadow (product-media plan

        Phase 6.1).
    ProductVariantAxisOut:
      properties:
        key:
          type: string
          title: Key
        values:
          items:
            type: string
          type: array
          title: Values
          default: []
      type: object
      required:
        - key
      title: ProductVariantAxisOut
      description: >-
        One variant-selector axis + its ordered distinct values.


        Derived from the visible variants: an axis is an ``options`` key that
        takes

        more than one distinct value across the (non-hidden) siblings. A key
        that is

        constant across every variant is a product-level descriptor, not a

        selectable axis, so it is excluded. Both key order and value order
        preserve

        first-seen order so a storefront renders the selector deterministically.
    ProductVariantOut:
      properties:
        sku:
          type: string
          title: Sku
        options:
          type: object
          title: Options
        attributes:
          type: object
          title: Attributes
        list_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: List Unit Price
        customer_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Customer Unit Price
        has_customer_rate:
          type: boolean
          title: Has Customer Rate
          default: false
        compare_at_unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Compare At Unit Price
        discount_percent:
          anyOf:
            - type: integer
            - type: "null"
          title: Discount Percent
        availability:
          type: string
          title: Availability
          default: UNKNOWN
        badges:
          items:
            type: string
          type: array
          title: Badges
          default: []
      type: object
      required:
        - sku
      title: ProductVariantOut
      description: >-
        One selectable variant of a product (a sibling sellable SKU sharing the

        same ``base_product_code``).


        ``attributes`` is the variation's axis->value map (e.g. ``{"colour":
        "red",

        "size": "M"}``) a storefront's variant selector matches a selection
        against.

        Each variant carries its own list price, the customer overlay (only when
        a

        shopper token rode the request, Phase 6.1), and a coarse availability
        band.

        A SKU hidden on the storefront surface (product OR variation) never
        appears

        here (OMS drops it upstream, rich-pdp plan Phase 8.1).
    PromoCodeIn:
      properties:
        code:
          anyOf:
            - type: string
              maxLength: 64
            - type: "null"
          title: Code
      type: object
      title: PromoCodeIn
      description: |-
        ``POST /v1/carts/{id}/promotions`` — attach (or, with an empty code,
        clear) a promo code on the cart.
    ShippingCourierOptionOut:
      properties:
        courier_id:
          type: string
          title: Courier Id
        name:
          type: string
          title: Name
        slug:
          type: string
          title: Slug
        pickup_locations:
          items:
            $ref: "#/components/schemas/ShippingPickupLocationOut"
          type: array
          title: Pickup Locations
          default: []
        customer_fields:
          items:
            $ref: "#/components/schemas/ShippingFieldOut"
          type: array
          title: Customer Fields
          default: []
      type: object
      required:
        - courier_id
        - name
        - slug
      title: ShippingCourierOptionOut
      description: One enabled courier as an ECOMMERCE checkout choice.
    ShippingFieldOut:
      properties:
        key:
          type: string
          title: Key
        label:
          type: string
          title: Label
        data_type:
          type: string
          title: Data Type
        options:
          anyOf:
            - items:
                type: string
              type: array
            - type: "null"
          title: Options
        required:
          type: boolean
          title: Required
          default: true
        sort:
          type: integer
          title: Sort
          default: 0
      type: object
      required:
        - key
        - label
        - data_type
      title: ShippingFieldOut
      description: A ``customer_at_checkout`` field the shopper fills (e.g. an MCE number).
    ShippingOptionsOut:
      properties:
        couriers_enabled:
          type: boolean
          title: Couriers Enabled
          default: false
        items:
          items:
            $ref: "#/components/schemas/ShippingCourierOptionOut"
          type: array
          title: Items
          default: []
      type: object
      title: ShippingOptionsOut
      description: >-
        ``GET /v1/checkout/shipping-options`` — the tenant's shipping step. With

        the module off (or no courier enabled) ``couriers_enabled`` is ``False``
        /

        ``items`` is empty and the storefront renders no shipping step
        (Invariant 8);

        a carrier selection is required at ``POST /v1/checkout`` only when an
        option

        exists.
    ShippingPickupLocationOut:
      properties:
        id:
          type: string
          title: Id
        name:
          type: string
          title: Name
        address:
          anyOf:
            - type: string
            - type: "null"
          title: Address
        notes:
          anyOf:
            - type: string
            - type: "null"
          title: Notes
      type: object
      required:
        - id
        - name
      title: ShippingPickupLocationOut
      description: >-
        A pickup point the shopper can choose at checkout (a non-empty list
        means

        render a selector). Empty for a MyCart-shaped courier.
    StorefrontOrderDetailOut:
      properties:
        order_id:
          type: string
          title: Order Id
        status:
          type: string
          title: Status
        channel:
          anyOf:
            - type: string
            - type: "null"
          title: Channel
        subtotal:
          anyOf:
            - type: number
            - type: "null"
          title: Subtotal
        gct_amount:
          anyOf:
            - type: number
            - type: "null"
          title: Gct Amount
        total:
          anyOf:
            - type: number
            - type: "null"
          title: Total
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        created_at:
          anyOf:
            - type: string
            - type: "null"
          title: Created At
        confirmed_at:
          anyOf:
            - type: string
            - type: "null"
          title: Confirmed At
        completed_at:
          anyOf:
            - type: string
            - type: "null"
          title: Completed At
        fulfillment_status:
          anyOf:
            - type: string
            - type: "null"
          title: Fulfillment Status
        order_status:
          anyOf:
            - type: string
            - type: "null"
          title: Order Status
        delivery:
          anyOf:
            - $ref: "#/components/schemas/OrderDeliveryOut"
            - type: "null"
        lines:
          items:
            $ref: "#/components/schemas/StorefrontOrderLineOut"
          type: array
          title: Lines
          default: []
        status_history:
          items:
            $ref: "#/components/schemas/OrderStatusEventOut"
          type: array
          title: Status History
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      required:
        - order_id
        - status
      title: StorefrontOrderDetailOut
      description: >-
        ``GET /v1/orders/{id}`` — lines + status history (the pick/ship
        timeline).


        Extends ``_OrderCommon`` (Phase 6.4) - the leading ``order_id`` /
        ``status`` /

        ``channel`` are shared with ``StorefrontOrderRowOut`` verbatim.
    StorefrontOrderLineOut:
      properties:
        line_id:
          anyOf:
            - type: string
            - type: "null"
          title: Line Id
        sku:
          type: string
          title: Sku
        description:
          anyOf:
            - type: string
            - type: "null"
          title: Description
        order_uom:
          anyOf:
            - type: string
            - type: "null"
          title: Order Uom
        order_quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Order Quantity
        quantity:
          anyOf:
            - type: number
            - type: "null"
          title: Quantity
        unit_price:
          anyOf:
            - type: number
            - type: "null"
          title: Unit Price
        line_total:
          anyOf:
            - type: number
            - type: "null"
          title: Line Total
      type: object
      required:
        - sku
      title: StorefrontOrderLineOut
      description: >-
        One line of a confirmed order (mirrored from OMS). ``line_id`` is
        nullable

        here because it mirrors the OMS row (contrast ``CartLineOut.line_id``,
        always

        present). ``quantity`` is the base-unit quantity, sourced from the OMS
        line's

        ``requested_quantity`` field and re-exposed under the wire-consistent
        name

        ``quantity`` (storefront-cleanup plan Phase 6.1).
    StorefrontOrderRowOut:
      properties:
        order_id:
          type: string
          title: Order Id
        status:
          type: string
          title: Status
        channel:
          anyOf:
            - type: string
            - type: "null"
          title: Channel
        total:
          anyOf:
            - type: number
            - type: "null"
          title: Total
        currency:
          anyOf:
            - type: string
            - type: "null"
          title: Currency
        created_at:
          anyOf:
            - type: string
            - type: "null"
          title: Created At
        line_count:
          type: integer
          title: Line Count
          default: 0
      type: object
      required:
        - order_id
        - status
      title: StorefrontOrderRowOut
    StorefrontOrdersOut:
      properties:
        orders:
          items:
            $ref: "#/components/schemas/StorefrontOrderRowOut"
          type: array
          title: Orders
          default: []
        total:
          type: integer
          title: Total
          default: 0
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: StorefrontOrdersOut
      description: "``GET /v1/orders`` — the shopper's own order history (OMS auto-scopes)."
    TagGroupOut:
      properties:
        id:
          type: string
          title: Id
        name:
          type: string
          title: Name
        slug:
          anyOf:
            - type: string
            - type: "null"
          title: Slug
        is_exclusive:
          type: boolean
          title: Is Exclusive
          default: false
        tags:
          items:
            $ref: "#/components/schemas/TagOut"
          type: array
          title: Tags
          default: []
      type: object
      required:
        - id
        - name
      title: TagGroupOut
      description: >-
        One faceted tag group with its tags nested. ``is_exclusive`` marks a
        group

        whose tags are mutually exclusive on a product (single-select facet).
    TagGroupsOut:
      properties:
        groups:
          items:
            $ref: "#/components/schemas/TagGroupOut"
          type: array
          title: Groups
          default: []
        errors:
          items:
            $ref: "#/components/schemas/DownstreamErrorOut"
          type: array
          title: Errors
          default: []
      type: object
      title: TagGroupsOut
      description: >-
        ``GET /v1/tags`` - the tenant's grouped tag taxonomy (the faceted

        counterpart to ``GET /v1/categories``), sourced from inventory. Degrades
        to an

        empty list + an ``errors[]`` entry when the taxonomy read fails.
    TagOut:
      properties:
        id:
          type: string
          title: Id
        name:
          type: string
          title: Name
        slug:
          anyOf:
            - type: string
            - type: "null"
          title: Slug
      type: object
      required:
        - id
        - name
      title: TagOut
      description: One tag within a group (storefront-cleanup plan Phase 5.1).
    UomOptionOut:
      properties:
        uom_code:
          type: string
          title: Uom Code
        stock_per_unit:
          anyOf:
            - type: number
            - type: "null"
          title: Stock Per Unit
        role:
          anyOf:
            - type: string
            - type: "null"
          title: Role
        is_default_sell:
          type: boolean
          title: Is Default Sell
          default: false
      type: object
      required:
        - uom_code
      title: UomOptionOut
      description: An alternate selling/receiving unit for a product.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    WebhookPingOut:
      properties:
        subscription_id:
          type: string
          title: Subscription Id
        event_type:
          type: string
          title: Event Type
        outcome:
          type: string
          title: Outcome
      type: object
      required:
        - subscription_id
        - event_type
        - outcome
      title: WebhookPingOut
      description: "``POST /v1/webhooks/{id}/ping`` — the outcome of a test delivery."
    WebhookSubscriptionCreate:
      properties:
        endpoint_url:
          type: string
          maxLength: 2048
          minLength: 1
          title: Endpoint Url
        event_types:
          items:
            type: string
          type: array
          minItems: 1
          title: Event Types
      type: object
      required:
        - endpoint_url
        - event_types
      title: WebhookSubscriptionCreate
      description: "``POST /v1/webhooks`` body — register an endpoint for a set of events."
    WebhookSubscriptionOut:
      properties:
        id:
          type: string
          title: Id
        endpoint_url:
          type: string
          title: Endpoint Url
        event_types:
          items:
            type: string
          type: array
          title: Event Types
        status:
          type: string
          title: Status
        created_at:
          type: string
          title: Created At
        updated_at:
          type: string
          title: Updated At
        signing_secret:
          anyOf:
            - type: string
            - type: "null"
          title: Signing Secret
      type: object
      required:
        - id
        - endpoint_url
        - event_types
        - status
        - created_at
        - updated_at
      title: WebhookSubscriptionOut
      description: |-
        A webhook subscription. ``signing_secret`` is present **only** on the
        create response (reveal-once) and ``None`` on every list/get read.
    WebhookSubscriptionUpdate:
      properties:
        endpoint_url:
          anyOf:
            - type: string
              maxLength: 2048
              minLength: 1
            - type: "null"
          title: Endpoint Url
        event_types:
          anyOf:
            - items:
                type: string
              type: array
              minItems: 1
            - type: "null"
          title: Event Types
        status:
          anyOf:
            - type: string
            - type: "null"
          title: Status
      type: object
      title: WebhookSubscriptionUpdate
      description: "``PATCH /v1/webhooks/{id}`` body — all fields optional."
tags:
  - name: catalog
    description: Anonymous product / category / tag reads (publishable key).
  - name: availability
    description: Coarse stock band (in-stock / low / out) — never a raw count.
  - name: customer
    description: Shopper session bootstrap + customer-priced catalog (shopper token).
  - name: cart
    description: Cart create / line edit / price / promo (shopper token).
  - name: checkout
    description: Cart → confirmed order via the OMS saga (ECOMMERCE channel).
  - name: orders
    description: Own-order tracking + cancel (shopper token).
  - name: invoices
    description: Own invoices + AR / credit standing (shopper token).
  - name: webhooks
    description: Signed-webhook subscription management (secret key).
  - name: diagnostics
    description: Key-edge smoke check — confirm a key is wired correctly.
