---
openapi: 3.1.0
info:
  title: Bolder API v2
  version: '2.0'
  description: |
    ## Getting Started

    The Bolder API provides programmatic access to your Bolder Shop data through a
    hypermedia-driven REST interface.

    ### Fastest path — Personal Access Token

    For scripts, CLI tools, and personal automation, skip the OAuth grant dance entirely:

    1. Go to [https://auth.onbolder.com/dev/credentials](https://auth.onbolder.com/dev/credentials)
       and sign in. The first visit auto-creates a default developer application for your
       account — there's nothing to register.
    2. Under **Personal Access Tokens**, create a new token: pick the scopes you need (see
       [Token scopes](#token-scopes) below) and an expiry.
    3. Copy the token — it's shown **once**.

    ```bash
    export BOLDER_TOKEN=PASTE_YOUR_TOKEN_HERE

    curl -H "Authorization: Bearer $BOLDER_TOKEN" https://api.onbolder.com/v2
    ```

    All `curl` examples in this documentation use `$BOLDER_TOKEN`. Set it once in your
    shell and every example works immediately. See [Personal Access Tokens](#personal-access-tokens-pats)
    below for details on expiry and revocation.

    ### Building a real integration — register an OAuth application

    If you're building something you'll distribute to other Bolder merchants (rather than
    automating your own account), register a dedicated application instead of using a PAT:

    Go to [https://auth.onbolder.com](https://auth.onbolder.com), sign in, and create a new
    application. You'll receive a `CLIENT_ID` and `CLIENT_SECRET`. The fastest way to get a
    token from a terminal is the **Client Credentials** grant:

    ```bash
    export BOLDER_TOKEN=$(curl -s \
      -X POST https://auth.onbolder.com/oauth/token \
      -d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=products.read,orders.read,orders.write" \
      | jq -r .access_token)
    ```

    No `jq`? Use Python instead:

    ```bash
    export BOLDER_TOKEN=$(curl -s \
      -X POST https://auth.onbolder.com/oauth/token \
      -d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=products.read,orders.read,orders.write" \
      | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
    ```

    > **Note:** Unlike PATs, these tokens are temporary. Re-run the request above when your
    > token expires, or use the `refresh_token` grant described in the Authentication section
    > below.

    ---

    ## Authentication

    The Bolder API uses **OAuth 2.0**. The token **must** be sent in the
    `Authorization` header — query-parameter tokens are not accepted by v2.

    Five grant types are supported:

    ### 1. Authorization Code (recommended for web apps)

    Redirect the user to the authorization endpoint and exchange the returned `code`
    for tokens on your server.

    ```
    GET https://auth.onbolder.com/oauth/authorize
      ?client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourapp.com/callback
      &response_type=code
      &scope=products.read,orders.read,orders.write
    ```

    Then exchange the code:

    ```
    POST https://auth.onbolder.com/oauth/token
    Content-Type: application/x-www-form-urlencoded

    grant_type=authorization_code
    &client_id=YOUR_CLIENT_ID
    &client_secret=YOUR_CLIENT_SECRET
    &redirect_uri=https://yourapp.com/callback
    &code=RETURNED_CODE
    ```

    ### 2. Implicit Grant (for client-side JavaScript apps)

    ```
    GET https://auth.onbolder.com/oauth/authorize
      ?client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourapp.com/callback
      &response_type=token
      &scope=products.read
    ```

    The access token is returned in the URL fragment after the redirect.

    ### 3. Client Credentials (for server-to-server / automated scripts)

    No user involved — the application authenticates as itself.

    ```
    POST https://auth.onbolder.com/oauth/token
    Content-Type: application/x-www-form-urlencoded

    grant_type=client_credentials
    &client_id=YOUR_CLIENT_ID
    &client_secret=YOUR_CLIENT_SECRET
    &scope=products.read,orders.read,orders.write
    ```

    ### 4. Resource Owner Password Credentials (trusted apps only)

    ```
    POST https://auth.onbolder.com/oauth/token
    Content-Type: application/x-www-form-urlencoded

    grant_type=password
    &client_id=YOUR_CLIENT_ID
    &client_secret=YOUR_CLIENT_SECRET
    &username=USER_EMAIL
    &password=USER_PASSWORD
    &scope=products.read,orders.read,orders.write
    ```

    ### 5. JWT Bearer (refreshing expired tokens)

    Exchange a `refresh_token` for a new access token:

    ```
    POST https://auth.onbolder.com/oauth/token
    Content-Type: application/x-www-form-urlencoded

    grant_type=refresh_token
    &client_id=YOUR_CLIENT_ID
    &client_secret=YOUR_CLIENT_SECRET
    &refresh_token=YOUR_REFRESH_TOKEN
    ```

    ### Token response

    All flows return a JSON token response:

    ```json
    {
      "access_token":  "eyJ...",
      "token_type":    "bearer",
      "expires_in":    7200,
      "refresh_token": "abc123...",
      "scope":         "products.read,orders.read,orders.write"
    }
    ```

    Access tokens are **temporary**. Implement refresh-token logic to obtain new
    tokens before they expire.

    ### Token scopes

    v2 uses **granular scopes**: request access to individual resource namespaces using
    `{namespace}.read` / `{namespace}.write` scopes. This lets an integration ask for only
    what it needs — e.g. an order-fulfillment tool can request `orders.read,orders.write`
    without also getting product or customer write access.

    | Namespace   | Read scope        | Write scope         | Covers |
    |-------------|--------------------|-----------------------|--------|
    | `products`  | `products.read`   | `products.write`    | Products, variants, collections |
    | `orders`    | `orders.read`     | `orders.write`       | Orders, documents, tracking |
    | `customers` | `customers.read`  | `customers.write`    | Customer profiles |
    | `shops`     | `shops.read`      | `shops.write`        | Shop configuration |
    | `hub`       | `hub.read`        | `hub.write`           | Content (posts, pages) |
    | `store`     | `store.read`      | `store.write`        | Themes and assets |
    | `sellers`   | `sellers.read`    | — (read-only)         | Seller information |
    | `batches`   | `batches.read`    | `batches.write`      | Batch job status |

    Request them the same way as any other scope, comma-separated:

    ```
    &scope=products.read,orders.read,orders.write
    ```

    ### Personal Access Tokens (PATs)

    For scripts and CLI tools where the standard OAuth grants (which require redirect flows or
    a client secret exchange per session) are overkill, you can issue a **Personal Access
    Token**: a long-lived Bearer JWT tied to your user account and a specific application,
    created from the developer credentials page — see
    [Fastest path — Personal Access Token](#fastest-path-personal-access-token) above for the
    step-by-step. A few more details:

    - Give the token a name, choose an expiry (30 days, 90 days, 1 year, or no expiry), and
      select the scopes to grant — you can only grant scopes your own user role already has
      (see [Token scopes](#token-scopes) above).
    - The raw token is shown **once**, immediately after creation. Copy it then — it cannot be
      retrieved again afterwards.

    Use it exactly like any other access token:

    ```bash
    curl -H "Authorization: Bearer $PERSONAL_ACCESS_TOKEN" https://api.onbolder.com/v2
    ```

    PATs do not expire on a fixed short TTL like regular access tokens, so there is no
    refresh-token flow — the token is valid until its chosen expiry (if any) or until revoked.
    Revoke a PAT at any time from the same credentials page; revocation takes effect
    immediately.

    ---

    ## Hypermedia (HAL)

    All responses follow the [HAL](https://stateless.group/hal_specification.html)
    (Hypertext Application Language) format. Every response contains:

    - **`_links`** — navigation links to related resources and available actions.
    - **`_embedded`** — inline sub-resources (e.g. variants inside a product).
    - **`_class`** — array of type strings (e.g. `["results", "products"]`).
    - **Properties** — the resource's own data fields at the top level.

    ### CURIEs

    Link relation types use **service-namespaced CURIEs** so clients can
    resolve documentation for any relation:

    ```json
    {
      "_links": {
        "curies": [
          { "name": "products", "href": "https://api.onbolder.com/docs/v2#products:{rel}", "templated": true },
          { "name": "orders",   "href": "https://api.onbolder.com/docs/v2#orders:{rel}",   "templated": true }
        ],
        "self":             { "href": "https://api.onbolder.com/v2/products/123" },
        "products:update":  { "href": "https://api.onbolder.com/v2/products/123", "method": "put" },
        "products:variants":{ "href": "https://api.onbolder.com/v2/products/123/variants" }
      }
    }
    ```

    **Clients should follow links rather than constructing URLs.** If a link is absent
    from a response, the action is either unavailable or not permitted for the current
    token. The presence of a link is itself an affordance.

    ### URI Templates

    Some links are [URI templates](https://www.rfc-editor.org/rfc/rfc6570) (marked
    `"templated": true`). Expand them with the parameters shown in the template before
    making a request:

    ```
    products:list → https://api.onbolder.com/v2/products{?q,status,page,per_page,...}

    Expanded:       https://api.onbolder.com/v2/products?status=visible&page=2
    ```

    ---

    ## Pagination

    List endpoints return a standard pagination envelope:

    ```json
    {
      "_class":      ["results", "products"],
      "total_items": 142,
      "per_page":    20,
      "page":        1,
      "_links": {
        "self": { "href": "https://api.onbolder.com/v2/products?page=1" },
        "next": { "href": "https://api.onbolder.com/v2/products?page=2" },
        "prev": { "href": "https://api.onbolder.com/v2/products?page=0" }
      },
      "_embedded": {
        "items": [...]
      }
    }
    ```

    Default page size is **20**, maximum is **200** (use `per_page` to override).

    ---

    ## Errors

    | HTTP Status | Meaning |
    |-------------|---------|
    | `400` | Bad request — malformed JSON or missing required field |
    | `401` | Unauthorized — missing or invalid access token |
    | `403` | Forbidden — valid token but insufficient permissions |
    | `404` | Not found |
    | `422` | Unprocessable — validation failed; see `_embedded.errors` |
    | `429` | Too many requests — rate limit exceeded |
    | `500` | Server error |

    Validation errors (`422`) embed a structured error list:

    ```json
    {
      "_embedded": {
        "errors": [
          { "field": "name", "messages": ["can't be blank"] }
        ]
      }
    }
    ```

    ---

    ## HTTP caching

    The API emits standard `ETag` and `Last-Modified` headers. Clients may send
    `If-None-Match` / `If-Modified-Since` on subsequent requests; unchanged
    resources return `304 Not Modified` with no body.
  contact:
    name: Bolder API Support
    url: https://www.onbolder.com
servers:
- url: https://api.onbolder.com/v2
  description: Production
security:
- bearerAuth: []
tags:
- name: Root
  description: API entry point — embeds sellers, shops, and navigation links to every
    resource
- name: Sellers
  description: |
    Seller entities — the business layer between an account and its shops.
    An account can have multiple sellers, each owning one or more shops and their product catalog.
- name: Shops
  description: |
    Shops owned by the authenticated account. A shop is the customer-facing storefront
    that belongs to a seller. Settings, admins, shipping tables, payment methods,
    exchange rates, and other shop-level configuration are anchored to a specific shop.
- name: Content
  description: |
    CMS-style content for a shop's storefront — blog posts, static pages, and
    contact/lead-capture forms.
- name: Products
  description: |
    Catalog products, automatically scoped to the authenticated account's sellers.
    Account-scoped tokens need no extra parameters; tokens without an account/seller
    scope must supply `shop_subdomains`.
- name: Variants
  description: |
    Product variants (size, colour, etc.). Nested under `/products/{product_id}/variants`.
    Each variant holds its own SKU, price, stock level, and images.
- name: Price Lists
  description: |
    Named price lists for B2B / wholesale pricing. Products can have per-variant price
    overrides within a price list.
- name: Product Types
  description: Product type definitions used to group and filter catalog products.
- name: Collections
  description: |
    Curated product collections (manually or rule-based). Products can belong to
    multiple collections.
- name: Orders
  description: Shop orders. Supports batch status updates and per-order document management.
- name: Customers
  description: Shop customers. Includes customer authentication and password-reset
    flows.
- name: Promotions
  description: Promotional discount codes and automatic discounts applied at checkout.
- name: Volume Discounts
  description: Quantity-based discount rules (tiered pricing by units purchased).
- name: Themes
  description: |
    Hosted-storefront themes, templates, and assets. Applies only to shops with a
    hosted storefront (`store:` namespace); absent for headless shops.
- name: Webhooks
  description: |
    Event webhooks. Subscribe to events such as `orders.created`, `products.updated`, etc.
    Inactive subscriptions can be reactivated and their delivery history inspected.
- name: Subscriptions
  description: |
    Recurring product subscriptions. `SubscriptionFee` defines the recurring
    payment terms attached to a product/variant; `SubscriptionPurchase` is a
    customer's active subscription created from a recurring order.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    oauth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://auth.onbolder.com/oauth/token
          scopes: &1
            products.read: View products, variants and collections
            products.write: Create and update products
            orders.read: View orders
            orders.write: Create and update orders
            customers.read: View customer profiles
            customers.write: Update customer profiles
            shops.read: View shop configuration
            shops.write: Update shop configuration
            hub.read: View content (posts, pages)
            hub.write: Create and update content
            store.read: View themes and assets
            store.write: Manage themes and assets
            sellers.read: View seller information
            batches.read: View batch job status
            batches.write: Create and run batch jobs
        authorizationCode:
          authorizationUrl: https://auth.onbolder.com/oauth/authorize
          tokenUrl: https://auth.onbolder.com/oauth/token
          scopes: *1
  schemas:
    Pagination:
      type: object
      properties:
        total_items:
          type: integer
          example: 42
        per_page:
          type: integer
          example: 20
        page:
          type: integer
          example: 1
    HalLinks:
      type: object
      additionalProperties:
        oneOf:
        - "$ref": "#/components/schemas/HalLink"
        - type: array
          items:
            "$ref": "#/components/schemas/HalLink"
    HalLink:
      type: object
      required:
      - href
      properties:
        href:
          type: string
        templated:
          type: boolean
        method:
          type: string
          enum:
          - get
          - post
          - put
          - patch
          - delete
        title:
          type: string
        type:
          type: string
    ErrorResponse:
      type: object
      properties:
        message:
          type: string
          example: Access denied
        status:
          type: integer
          example: 403
    ValidationErrors:
      type: object
      properties:
        _embedded:
          type: object
          properties:
            errors:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  messages:
                    type: array
                    items:
                      type: string
    Root:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        _class:
          type: array
          items:
            type: string
          example:
          - root
        account_id:
          type: integer
        account_status:
          type: string
          example: active
        uid:
          type: integer
          description: User ID (absent for client_credentials tokens)
        email:
          type: string
        user_name:
          type: string
        provider:
          type: string
          example: bootic
        scopes:
          type: string
          example: products.read,orders.read,orders.write
        _embedded:
          type: object
          properties:
            sellers:
              type: array
              items:
                "$ref": "#/components/schemas/Seller"
            shops:
              type: array
              items:
                "$ref": "#/components/schemas/Shop"
    Shop:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        seller_id:
          type: integer
        account_id:
          type: integer
        name:
          type: string
        subdomain:
          type: string
        url:
          type: string
        description:
          type: string
        locale:
          type: string
        currency_code:
          type: string
        timezone:
          type: string
        asset_count:
          type: integer
        product_counts:
          type: object
          properties:
            visible:
              type: integer
            blocked:
              type: integer
            hidden:
              type: integer
        products_use_variant_prices:
          type: boolean
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    ShopList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - shops
          _embedded:
            type: object
            properties:
              shops:
                type: array
                items:
                  "$ref": "#/components/schemas/Shop"
    Seller:
      type: object
      description: 'A seller may own more than one shop. Use the `shops:list` link
        (`GET /v2/shops?seller_id={id}`) to enumerate all shops owned by the seller.

        '
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        account_id:
          type: integer
        locale:
          type: string
          example: en
        currency_code:
          type: string
          example: USD
        provides_shipping:
          type: boolean
        provides_pickup:
          type: boolean
        prices_include_tax:
          type: boolean
        tax_rate:
          type: number
          example: 0.19
        tax_name:
          type: string
          example: VAT
        products_count:
          type: integer
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    SellerList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - sellers
          _embedded:
            type: object
            properties:
              items:
                type: array
                items:
                  "$ref": "#/components/schemas/Seller"
    Product:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        name:
          type: string
          description: Product display name.
        slug:
          type: string
        class_name:
          type: string
        status:
          type: string
          enum:
          - visible
          - hidden
        description:
          type: string
        short_description:
          type: string
        is_bundle:
          type: boolean
        is_deliverable:
          type: boolean
        has_options:
          type: boolean
        tags:
          type: array
          items:
            type: string
        currency_code:
          type: string
          example: USD
        bundle_price_mode:
          type: string
          enum:
          - price
          - sum
        bundle_discount:
          type: number
        regular_price:
          type: number
          description: In cents
        price:
          type: number
          description: Effective price in cents
        sale_price:
          type: number
          description: In cents
        sale_price_starts_at:
          type: string
          format: date-time
        sale_price_ends_at:
          type: string
          format: date-time
        has_multiple_prices:
          type: boolean
        discount_percentage:
          type: number
        safety_stock:
          type: integer
        available:
          type: boolean
        stock:
          type: integer
          description: Current stock
        online_stock:
          type: integer
        all_stock:
          type: integer
          description: Stock across all locations
        vendor:
          type: string
        variant_options:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              value:
                type: string
        global_tags:
          type: array
          items:
            type: string
        stocks:
          type: object
          additionalProperties:
            type: integer
        stocks_per_location:
          type: object
          additionalProperties:
            type: integer
        cart_max_quantity:
          type: integer
        cart_quantity_multiple:
          type: integer
        list_prices:
          type: array
          items:
            type: object
            properties:
              price_list_id:
                type: integer
              price:
                type: number
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    ProductList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - products
          _embedded:
            type: object
            properties:
              products:
                type: array
                items:
                  "$ref": "#/components/schemas/Product"
    ProductInput:
      type: object
      required:
      - name
      - shop_id
      description: Input schema for creating or updating a product. `shop_id` and
        `name` are required on create; all fields are optional on update.
      properties:
        shop_id:
          type: integer
          description: ID of the shop (required on create)
        name:
          type: string
          description: Product name.
        slug:
          type: string
          description: URL slug. Auto-generated from name if omitted.
        status:
          type: string
          enum:
          - visible
          - hidden
          - blocked
          default: hidden
        class:
          type: string
          enum:
          - Product
          - ProductBundle
          - Subscription
          - GiftCard
          default: Product
          description: Product class. Defaults to `Product`.
        description:
          type: string
        short_description:
          type: string
        deliverable:
          type: boolean
          description: 'Whether the product requires physical delivery. Defaults to
            `true`. Set to `false` for digital goods, event tickets, subscriptions,
            etc. Returned as `is_deliverable` in responses.

            '
        tracks_stock:
          type: boolean
          description: Whether stock is tracked for this product's variants.
        has_prices_per_variant:
          type: boolean
          description: 'Required when variants have different prices. Must be `true`
            if any variant sets its own `regular_price`; the API returns 422 otherwise.

            '
        has_weights_per_variant:
          type: boolean
          description: Whether variants carry individual weights.
        regular_price:
          type: integer
          description: 'Regular price in cents, applied to all variants (for non-bundle
            products). Use per-variant `regular_price` inside `variants` when `has_prices_per_variant`
            is true.

            '
        sale_price:
          type: integer
          description: Sale price in cents, applied to all variants.
        sale_price_starts_at:
          type: string
          format: date-time
          description: ISO 8601 datetime when the sale price activates.
        sale_price_ends_at:
          type: string
          format: date-time
          description: ISO 8601 datetime when the sale price expires.
        product_type_name:
          type: string
          description: Product type name. Created automatically if it doesn't exist.
        vendor_name:
          type: string
          description: Vendor name. Created automatically if it doesn't exist.
        tags:
          type: array
          items:
            type: string
          description: Tag names. Replaces all existing tags unless `_policies.keep_attributes`
            is set.
        cart_quantity_multiple:
          type: integer
          description: Enforces cart quantities to be multiples of this value.
        variant_options:
          type: object
          description: Names for up to 3 variant option axes (e.g. "Color", "Size").
          properties:
            option1:
              type: string
              example: Color
            option2:
              type: string
              example: Size
            option3:
              type: string
        variants:
          type: array
          description: 'Variant definitions. By default, variants not present in the
            array are removed; set `_policies.keep_variants: true` to preserve them.

            '
          items:
            "$ref": "#/components/schemas/VariantInput"
        collections:
          type: array
          description: 'Collections to assign the product to. Pass `id` to reference
            an existing collection, or `title` to create/find by name. Replaces all
            existing collections unless `_policies.keep_collections: true`.

            '
          items:
            type: object
            properties:
              id:
                type: string
                description: Collection ID
              title:
                type: string
                description: Collection title (used to find or create)
        custom_attributes:
          type: array
          description: Custom meta-field attributes.
          items:
            type: object
            properties:
              name:
                type: string
              value:
                type: string
              field_type:
                type: string
                enum:
                - string
                - int
                - split_string
                - text
              field_group:
                type: string
              required:
                type: boolean
                default: false
              filterable:
                type: boolean
                default: true
        volume_discount_id:
          type: integer
          description: Assigns an existing volume discount to this product.
        relations:
          type: array
          description: 'Related products (e.g. "goes well with"). Replaces all existing
            relations unless `_policies.keep_relations: true`.

            '
          items:
            type: object
            properties:
              id:
                type: string
                description: Relation ID, when updating an existing relation.
              name:
                type: string
                description: Relation name (e.g. "related", "upsell").
              child_id:
                type: string
                description: Related product ID or slug.
              child_type:
                type: string
        custom_variant_options:
          type: object
          description: 'Custom variant option metadata (id/name/type) for up to 3
            option axes, keyed by `option1`/`option2`/`option3`.

            '
        bundle_options:
          type: array
          description: 'Configurable bundle option groups (customer picks items from
            each group). Only applies to `class: ProductBundle`.

            '
          items:
            type: object
            properties:
              id:
                type: integer
                description: Bundle option ID, when updating.
              name:
                type: string
              min_count:
                type: integer
              max_count:
                type: integer
                default: 1
              max_per_item:
                type: integer
              bundle_items:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                      description: Bundle item ID, when updating.
                    product_id:
                      type: integer
                    variant_id:
                      type: integer
                      description: Required unless `variant_sku` is given.
                    variant_sku:
                      type: string
                      description: Required unless `variant_id` is given.
                    units:
                      type: integer
                    additional_price:
                      type: integer
                      description: Additional price in cents if this item is selected.
        bundle_items:
          type: array
          description: 'Fixed bundle items (always included, no customer choice).
            Only applies to `class: ProductBundle`.

            '
          items:
            type: object
            properties:
              id:
                type: integer
                description: Bundle item ID, when updating.
              product_id:
                type: integer
              variant_id:
                type: integer
                description: Required unless `variant_sku` is given.
              variant_sku:
                type: string
                description: Required unless `variant_id` is given.
              units:
                type: integer
                default: 1
        _policies:
          type: object
          description: 'Control update behaviour. All flags default to `false` (destructive
            replace). Set to `true` to preserve existing data when the corresponding
            field is not included in the request.

            '
          properties:
            keep_variants:
              type: boolean
              default: false
              description: Keep existing variants not listed in `variants`.
            keep_collections:
              type: boolean
              default: false
              description: Keep existing collection assignments not listed in `collections`.
            keep_relations:
              type: boolean
              default: false
              description: Keep existing product relations not listed in `relations`.
            keep_attributes:
              type: boolean
              default: false
              description: Keep existing custom attributes not listed in `custom_attributes`.
            ensure_unique:
              type: string
              enum:
              - title
              - slug
              description: 'If set, the product is not created when another product
                already has the same value for this field (returns 409 instead).

                '
    VariantInput:
      type: object
      description: 'Variant definition for use inside `ProductInput.variants`. Include
        `id` to update an existing variant; omit it to create a new one. Variants
        are named via `option1`/`option2`/`option3`.

        '
      properties:
        id:
          type: integer
          description: Existing variant ID. Required when updating a specific variant.
        sku:
          type: string
        option1:
          type: string
        option2:
          type: string
        option3:
          type: string
        regular_price:
          type: integer
          description: Regular price in cents. Required per-variant when `has_prices_per_variant`
            is true.
        sale_price:
          type: integer
          description: Sale price in cents.
        online_stock:
          type: integer
          description: Available online stock units.
        weight_in_grams:
          type: integer
        barcode_number:
          type: string
        status:
          type: string
          enum:
          - visible
          - hidden
        available_if_no_stock:
          type: boolean
          default: false
          description: Allow purchase even when stock reaches zero.
    Variant:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        product_id:
          type: integer
        name:
          type: string
          description: Display name, computed from option1/option2/option3.
        status:
          type: string
          enum:
          - visible
          - hidden
        sku:
          type: string
        barcode_number:
          type: string
        option1:
          type: string
        option2:
          type: string
        option3:
          type: string
        currency_code:
          type: string
        regular_price:
          type: integer
          description: Regular price in cents
        sale_price:
          type:
          - integer
          - 'null'
          description: Sale price in cents
        price:
          type: integer
          description: Effective price in cents (sale_price if set, else regular_price)
        online_stock:
          type: integer
        all_stock:
          type: integer
        available_if_no_stock:
          type: boolean
        weight_in_grams:
          type: integer
        unit_quantity:
          type: number
        color:
          type: string
        cost:
          type: integer
        profit:
          type: integer
        stocks:
          type: array
          description: Per-location stock breakdown
          items:
            type: object
            properties:
              location_id:
                type: integer
              available_units:
                type: integer
              total_units:
                type: integer
    Promotion:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        name:
          type: string
        status:
          type: string
          enum:
          - active
          - inactive
        cumulative:
          type: boolean
          description: Whether this promotion can stack with other promotions.
        discount_type:
          type: string
          enum:
          - fixed
          - percentage
          - free-shipping
        discount:
          type: number
          description: Discount value — a percentage (0–100) or a fixed amount in
            cents.
        activation_mode:
          type: string
          enum:
          - code
          - auto
          - fulfillment
          - payment
        code:
          type: string
          description: Coupon code (present when `activation_mode` is `code`).
        payment_method_id:
          type: integer
          description: Payment method ID (present when `activation_mode` is `payment`).
        usage_limit:
          type: integer
        per_user_limit:
          type: integer
        usage_count:
          type: integer
          description: Total number of times this promotion has been used.
        starts_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        price_range_from:
          type: integer
          description: Minimum order subtotal in cents for the promotion to apply.
        price_range_to:
          type: integer
          description: Maximum order subtotal in cents for the promotion to apply.
        product_range_from:
          type: integer
          description: Minimum number of qualifying products in the cart.
        product_range_to:
          type: integer
          description: Maximum number of qualifying products in the cart.
        product_discount_group_id:
          type: integer
        product_discount_group_type:
          type: string
          enum:
          - AllProducts
          - AllBundles
          - Collection
          - ProductType
          - Vendor
          - Tag
          - Product
        product_discount_group_filter_mode:
          type: string
          enum:
          - all
          - all_except
          - single_cheapest
          - single_most_expensive
          - all_but_cheapest
          - all_but_most_expensive
          - cheapest_half
          - most_expensive_half
          - cheapest_third
          - most_expensive_third
          - cheapest_fourth
          - most_expensive_fourth
          - single_cheapest_from_count_group
          - single_most_expensive_from_count_group
          - cheapest_half_from_count_group
          - most_expensive_half_from_count_group
          - cheapest_third_from_count_group
          - most_expensive_third_from_count_group
          - cheapest_fourth_from_count_group
          - most_expensive_fourth_from_count_group
        product_count_group_id:
          type: integer
          description: Group used to count qualifying products (for count-gated discounts).
        product_count_group_type:
          type: string
          enum:
          - AllProducts
          - AllBundles
          - Collection
          - ProductType
          - Vendor
          - Tag
          - Product
        apply_tag:
          type: string
          description: Tag applied to the order when this promotion is used.
        available_for:
          type: string
          description: Restriction on who can use the promotion (e.g. contact group).
    PromotionInput:
      type: object
      required:
      - name
      - shop_id
      - discount_type
      - discount
      - activation_mode
      description: 'Input for creating or updating a promotion. On update (PUT) all
        fields are optional.

        '
      properties:
        shop_id:
          type: integer
          description: ID of the shop (required on create)
        name:
          type: string
        status:
          type: string
          enum:
          - active
          - inactive
          default: active
        cumulative:
          type: boolean
          default: true
          description: Whether this promotion can stack with others. Defaults to `true`.
        discount_type:
          type: string
          enum:
          - fixed
          - percentage
          - free-shipping
        discount:
          type: number
          description: Discount value — a percentage (0–100) or a fixed amount in
            cents.
        activation_mode:
          type: string
          enum:
          - code
          - auto
          - fulfillment
          - payment
          description: 'How the promotion is activated. `code` requires a coupon code;
            `auto` applies automatically to eligible orders; `fulfillment` activates
            for a specific fulfillment method; `payment` activates for a specific
            payment method.

            '
        code:
          type: string
          description: Coupon code (required when `activation_mode` is `code`).
        fulfillment_method:
          type: integer
          description: Fulfillment method ID (required when `activation_mode` is `fulfillment`).
        payment_method_id:
          type: integer
          description: Payment method ID (required when `activation_mode` is `payment`).
        usage_limit:
          type: integer
          description: Maximum total redemptions. Unlimited if omitted.
        per_user_limit:
          type: integer
          description: Maximum redemptions per customer. Unlimited if omitted.
        starts_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        price_range_from:
          type: integer
          description: Minimum order subtotal in cents for the promotion to apply.
        price_range_to:
          type: integer
          description: Maximum order subtotal in cents for the promotion to apply.
        product_range_from:
          type: integer
          description: Minimum number of qualifying products required in the cart.
        product_range_to:
          type: integer
          description: Maximum number of qualifying products allowed in the cart.
        product_discount_group_id:
          type: integer
          description: ID of the group whose products receive the discount.
        product_discount_group_type:
          type: string
          enum:
          - AllProducts
          - AllBundles
          - Collection
          - ProductType
          - Vendor
          - Tag
          - Product
        product_discount_group_filter_mode:
          type: string
          enum:
          - all
          - all_except
          - single_cheapest
          - single_most_expensive
          - all_but_cheapest
          - all_but_most_expensive
          - cheapest_half
          - most_expensive_half
          - cheapest_third
          - most_expensive_third
          - cheapest_fourth
          - most_expensive_fourth
          - single_cheapest_from_count_group
          - single_most_expensive_from_count_group
          - cheapest_half_from_count_group
          - most_expensive_half_from_count_group
          - cheapest_third_from_count_group
          - most_expensive_third_from_count_group
          - cheapest_fourth_from_count_group
          - most_expensive_fourth_from_count_group
          description: Which items within the discount group receive the discount.
        product_count_group_id:
          type: integer
          description: Group used to count qualifying products (for count-gated discounts).
        product_count_group_type:
          type: string
          enum:
          - AllProducts
          - AllBundles
          - Collection
          - ProductType
          - Vendor
          - Tag
          - Product
        price_list_id:
          type: integer
          description: Restrict the promotion to orders using this price list.
        apply_tag:
          type: string
          description: Tag to apply to the order when this promotion is used.
    VolumeDiscount:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        name:
          type: string
        discount_type:
          type: string
        apply_mode:
          type: string
        repeating_tier:
          type: boolean
        tiers:
          type: object
          description: Keyed by tier boundary — not an array.
        cumulative:
          type: boolean
        cumulative_mode:
          type: string
        available_for:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    VolumeDiscountInput:
      type: object
      required:
      - name
      - shop_id
      - tiers
      properties:
        shop_id:
          type: integer
          description: ID of the shop to create the volume discount in
        name:
          type: string
        discount_type:
          type: string
          description: Discount type.
        cumulative_mode:
          type: string
          enum:
          - within_product
          - global
          description: Omit for non-cumulative
        apply_on_tier_start_only:
          type: boolean
        tiers:
          type: array
          items:
            type: object
            properties:
              from:
                type: integer
                description: Units in cart at which this tier starts applying
              value:
                type: integer
                description: Discount value for this tier
            example:
              from: 5
              value: 10
    Webhook:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        channel_id:
          type: integer
        topic:
          type: string
          example: orders.created
        url:
          type: string
          example: https://myapp.com/events
        status:
          type: string
          enum:
          - active
          - failed_activation
          - failed
          - disabled
          description: |
            `active` — receiving deliveries normally.
            `failed_activation` — the initial activation handshake failed.
            `failed` — disabled automatically after 100 consecutive non-2xx delivery
            responses (see `error_count`); can be re-enabled via
            `PUT /webhooks/{id}/reactivate`.
            `disabled` — the target URL returned `410 Gone`; disabled immediately,
            with no retry threshold. Also reactivatable via the same endpoint.
        auth:
          type: object
        notify_origin:
          type: boolean
        app_id:
          type: integer
        error_count:
          type: integer
        last_error:
          type: string
        last_error_on:
          type: string
          format: date-time
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    WebhookList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - hubSubscriptions
          _embedded:
            type: object
            properties:
              subscriptions:
                type: array
                items:
                  "$ref": "#/components/schemas/Webhook"
    WebhookInput:
      type: object
      required:
      - topic
      - url
      properties:
        topic:
          type: string
          example: orders.created
          description: |
            Supported topics: `orders.created`, `orders.updated`,
            `orders.updated.closed`, `orders.updated.shipped`,
            `contacts.created`, `contacts.updated`, `products.created`,
            `products.updated`, `products.deleted`
        url:
          type: string
          example: https://myapp.com/webhook
        notify_origin:
          type: boolean
          default: false
          description: If false, does not notify the request-originating host
        auth:
          type: object
          properties:
            type:
              type: string
              enum:
              - none
              - basic
              default: none
            username:
              type: string
            password:
              type: string
    Order:
      type: object
      description: 'Field set is a representative subset of what the API returns —
        the order serializer also emits additional computed/analytics fields (e.g.
        `session_info`, per-line-item `discounts`/`surcharges`, `line_item_changes`)
        not listed here.

        '
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        code:
          type: string
          description: Order code, used as the path identifier (e.g. `/v2/orders/{code}`)
        external_id:
          type:
          - string
          - 'null'
          description: |
            Optional client-supplied reference, unique per seller. Set it at
            create time (or via update) to look the order back up later with
            `GET /v2/orders?external_id={value}` — unlike `id`/`code`, a
            colliding `external_id` is rejected with a 422 instead of being
            silently reassigned, so it's safe to use for dedup checks before
            retrying a create.
        status:
          type: string
          enum:
          - draft
          - checkout
          - in_review
          - pending
          - cancelled
          - closed
          - completed
        fulfillment_status:
          type: string
          enum:
          - none
          - queued
          - preparing
          - ready
          - shipped
          - delivered
          - returned
        checkout_url:
          type: string
          description: Present only when the order is in `checkout` or `pending` status
        archived:
          type: boolean
        currency_code:
          type: string
        prices_include_tax:
          type: boolean
        items_net_total:
          type: integer
          description: Line items subtotal in cents, before discounts
        items_discount_total:
          type: integer
        items_total:
          type: integer
          description: Line items subtotal in cents, after discounts
        net_total:
          type: integer
          description: Order total in cents before tax
        discount_total:
          type: integer
        cart_total:
          type: integer
        surcharge_total:
          type: integer
        shipping_total:
          type: integer
        shipping_discount:
          type: integer
        shipping_minus_discount:
          type: integer
        tax_rate:
          type: number
          description: Present only when `prices_include_tax` is false
        tax_total:
          type: integer
          description: Present only when `prices_include_tax` is false
        total:
          type: integer
          description: Grand total in cents, including shipping, discounts and tax
        total_paid:
          type: integer
        total_refunded:
          type: integer
        max_refundable_amount:
          type: integer
        partial_payment_offered:
          type: boolean
          description: 'Whether this order is eligible for buyer-chosen partial payment:
            either the shop has it enabled for everyone, or this specific order was
            offered it via the API (`partial_payment_offered: true` on create/update).
            Either way, the order must not require review and any configured cart
            requirements must be met. Normally also requires a token-based payment
            method to exist, so the balance can be auto-charged later - unless the
            shop allows manual (non-token) payment methods for this, in which case
            the balance is instead collected via a reminder email. Only present when
            fetching a single order, not in list results.

            '
        paying_partially:
          type: boolean
          description: Whether the buyer opted into a partial payment for this order.
        partial_payment_percentage:
          type: integer
          description: Percentage of the total charged upfront. Only present when
            `paying_partially` is true.
        partial_payment_amount:
          type: number
          description: 'Amount charged upfront, in cents. Only present when fetching
            a single order, and only when partial payment is offered or active.

            '
        partial_payment_due_on:
          type: string
          format: date
          description: Date the remaining balance is due. Only present when `paying_partially`
            is true.
        shipping_description:
          type: string
        tracking_code:
          type: string
        courier_name:
          type: string
        payment_info:
          type: object
          description: Present only when the order has an associated payment method.
          properties:
            id:
              type: integer
            name:
              type: string
            type:
              type: string
              description: Payment method type/gateway identifier (e.g. `webpay_rest`,
                `mercado_pago`).
            data:
              type: object
              description: Gateway-specific transaction data, shape varies by `type`.
        requested_document_type:
          type: string
          description: 'Tax document type requested by the buyer at checkout (e.g.
            `invoice`, `receipt`), if any. Derived from custom order data, not a plain
            column.

            '
        tags:
          type: array
          items:
            type: string
        tag_ids:
          type: array
          items:
            type: integer
        allowed_statuses:
          type: array
          items:
            type: string
        allowed_fulfillment_statuses:
          type: array
          items:
            type: string
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
        checkout_on:
          type:
          - string
          - 'null'
          format: date-time
        pending_on:
          type:
          - string
          - 'null'
          format: date-time
        closed_on:
          type:
          - string
          - 'null'
          format: date-time
        shipped_on:
          type:
          - string
          - 'null'
          format: date-time
        _embedded:
          type: object
          description: "`contact` is present when the order has a customer attached.
            `company` is present when a company/tax entity is attached (e.g. via `company_name`
            on create/update). `address`/`billing_address` are present when set. `line_items`
            is always present. `payments`/`refunds`/`documents` are present only when
            non-empty.\n"
          properties:
            contact:
              type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
                email:
                  type: string
                phone_number:
                  type: string
            company:
              type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
                id_number:
                  type: string
                  description: Formatted tax/ID number (RUT, DNI, etc.)
                activity_code:
                  type: string
                address:
                  "$ref": "#/components/schemas/Address"
            address:
              "$ref": "#/components/schemas/Address"
            billing_address:
              "$ref": "#/components/schemas/Address"
            line_items:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: integer
                  product_id:
                    type: integer
                  product_name:
                    type: string
                  variant_id:
                    type: integer
                  variant_name:
                    type: string
                  variant_sku:
                    type: string
                  units:
                    type: integer
                  unit_price:
                    type: integer
                    description: Unit price in cents
                  net_total:
                    type: integer
                    description: Line total in cents before discounts
                  total:
                    type: integer
                    description: Line total in cents after discounts/surcharges
            payments:
              type: array
              items:
                "$ref": "#/components/schemas/OrderTransaction"
            refunds:
              type: array
              items:
                "$ref": "#/components/schemas/OrderTransaction"
            documents:
              type: array
              items:
                "$ref": "#/components/schemas/OrderDocument"
    Address:
      type: object
      properties:
        street:
          type: string
        street_2:
          type: string
        locality_name:
          type: string
        region_name:
          type: string
        country_name:
          type: string
        postal_code:
          type: string
    OrderTransaction:
      type: object
      description: A payment or refund transaction, embedded under `payments`/`refunds`
      properties:
        id:
          type: integer
        order_id:
          type: integer
        amount:
          type: integer
          description: Amount in cents (negative for refunds)
        absolute_amount:
          type: integer
          description: Absolute value of `amount` in cents
        successful:
          type: boolean
        card_token_id:
          type: integer
        payment_method_type:
          type: string
        transaction_id:
          type: string
          description: Gateway transaction ID
        authorization_code:
          type: string
        description:
          type: string
        created_at:
          type: string
          format: date-time
    OrderDocument:
      type: object
      properties:
        id:
          type: integer
        document_type:
          type: string
          enum:
          - invoice
          - receipt
        document_number:
          type: string
        file_url:
          type: string
    CheckoutSession:
      type: object
      properties:
        token:
          type: string
        status:
          type: string
        order_id:
          type: integer
        return_url:
          type:
          - string
          - 'null'
          description: 'Where the buyer is redirected after a hosted-redirect-mode
            gateway round-trip (see storefronts headless-reference.md). Set at most
            once per session lineage — the first non-blank `return_url` given to `POST
            /orders/{order_id}/checkout_session` wins; later calls while the same
            session is still active are ignored. Carried forward automatically if
            the session is later forked due to a stale cart total.

            '
        expires_at:
          type: string
          format: date-time
        last_viewed_at:
          type:
          - string
          - 'null'
          format: date-time
        view_count:
          type: integer
        checkout_url:
          type: string
    OrderInput:
      type: object
      description: Input schema for creating or updating an order.
      properties:
        shop_id:
          type: integer
          description: ID of the shop.
        status:
          type: string
          enum:
          - draft
          - checkout
          - in_review
          - pending
          - cancelled
          - closed
          - completed
          description: 'Order status. Requires `_policies.allow_status: true` to set
            most statuses. `closed` and `completed` are always allowed without the
            policy flag.

            '
        fulfillment_status:
          type: string
          enum:
          - none
          - queued
          - preparing
          - ready
          - shipped
          - delivered
          - returned
        fulfillment_method:
          type: string
          enum:
          - shipping
          - mailbox_pickup
          - local_pickup
          - none
        archived:
          type: boolean
        tags:
          type: array
          items:
            type: string
          description: 'Replaces all existing tags unless `_policies.append_tags:
            true`.'
        courier_name:
          type: string
        tracking_code:
          type: string
        shipping_amount:
          type: integer
          description: Shipping cost in cents.
        shipping_discount:
          type: integer
          description: Shipping discount in cents.
        shipping_description:
          type: string
        notify_status_change:
          type: boolean
          default: false
          description: Send a notification email to the customer on status change.
        notification_message:
          type: string
          description: Custom message to include in the status change notification
            email.
        partial_payment_offered:
          type: boolean
          description: 'Marks the order eligible for buyer-chosen partial payment
            -- only the buyer decides whether to actually use it, at checkout; there
            is no way to set `paying_partially` via this API. Works even on a shop
            that hasn''t enabled its own storefront cart opt-in for this.

            '
        partial_payment_due_on:
          type: string
          format: date
          description: 'Optional override (ISO 8601 date) of the balance due date,
            used with `partial_payment_offered: true`. Defaults to the shop-configured
            due date when omitted. Cannot be in the past.

            '
        contact:
          type: object
          description: 'Customer for this order. Pass `id` to link an existing contact,
            or `email` to find-or-create.

            '
          properties:
            id:
              type: string
              description: Existing contact ID
            name:
              type: string
            email:
              type: string
            phone_number:
              type: string
        address:
          type: object
          description: 'Shipping address. Pass `id` to clone an existing address,
            or provide fields to create a new one.

            '
          properties:
            id:
              type: string
              description: Existing address ID to clone
            street:
              type: string
            street_2:
              type: string
            locality_name:
              type: string
            region_name:
              type: string
            country_name:
              type: string
            postal_code:
              type: string
        line_items:
          type: array
          description: 'Line items to add. Use `variant_id` for catalog-linked items
            (stock-tracked); use `product_name` + `variant_sku` + `price` for detached
            items (no catalog required). Pass `units: -1` to remove an existing item.

            '
          items:
            type: object
            properties:
              variant_id:
                type: string
                description: Variant ID or SKU (links to catalog, decrements stock)
              variant_sku:
                type: string
                description: Variant SKU — alternative to `variant_id` for catalog
                  lookup
              product_name:
                type: string
                description: Product name for detached (non-catalog) line items
              variant_name:
                type: string
                description: Variant name for detached line items
              units:
                type: integer
                default: 1
                description: Quantity. Pass `-1` to remove the item.
              price:
                type: integer
                description: 'Override unit price in cents. For catalog items, requires
                  `_policies.force_item_prices: true`.

                  '
              subscription_fee_id:
                type: integer
                description: 'Subscription fee ID. When set on a catalog line item,
                  creates a subscription purchase when the order closes. Requires
                  `variant_id`.

                  '
        contact_data:
          type: array
          description: Arbitrary key/value data attached to the order contact (e.g.
            tax ID).
          items:
            type: object
            properties:
              key:
                type: string
              value:
                type: string
        _policies:
          type: object
          properties:
            allow_status:
              type: boolean
              default: false
              description: Allow setting restricted statuses (anything other than
                `closed`/`completed`).
            allow_no_stock:
              type: boolean
              default: false
              description: Create the order even if a variant is out of stock (order
                is tagged `sin-stock`).
            decrement_stock:
              type: boolean
              default: true
              description: Decrement variant stock when the order is created/updated.
            append_tags:
              type: boolean
              default: false
              description: Append `tags` to existing tags instead of replacing them.
            allow_promotions:
              type: boolean
              default: true
              description: Auto-apply eligible promotions to the order.
            force_item_prices:
              type: boolean
              default: false
              description: Use the `price` field from each line item instead of the
                product's price.
            fallback_to_retail_prices:
              type: boolean
              default: false
              description: When using a price list, fall back to retail prices for
                variants not in the list.
            allow_hidden_products:
              type: boolean
              default: false
              description: Allow adding variants from hidden products to the order.
    OrderList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - orders
          _embedded:
            type: object
            properties:
              orders:
                type: array
                items:
                  "$ref": "#/components/schemas/Order"
    Contact:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        email:
          type: string
        name:
          type: string
          description: Full name (first and last)
        phone_number:
          type: string
        phone_validated:
          type: boolean
        validation_method:
          type: string
        company_name:
          type: string
        company_id_number:
          type: string
          description: Company tax/ID number (RUT, DNI, etc.), formatted
        company_activity_code:
          type: string
        customer_status:
          type: string
          enum:
          - public
          - private
          - pending
          description: Customer account status.
        opt_in:
          type: boolean
          description: Whether the contact has opted in to marketing emails.
        source:
          type: string
        last_logged_on:
          type: string
          format: date-time
        last_ip_address:
          type: string
        has_password:
          type: boolean
        total_spent:
          type: integer
          description: In cents
        available_orders_count:
          type: integer
        salesperson_id:
          type: integer
        favorite_product_ids:
          type: array
          items:
            type: integer
        used_promotion_ids:
          type: array
          items:
            type: integer
        tags:
          type: array
          items:
            type: string
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
        _embedded:
          type: object
          properties:
            contact_group:
              type: object
              description: Present when the contact belongs to a group
            addresses:
              type: array
              items:
                "$ref": "#/components/schemas/Address"
    ContactInput:
      type: object
      required:
      - email
      - name
      description: 'Input schema for creating or updating a contact. On create, `email`
        and `name` are required. On update (PUT), all fields are optional. If a contact
        with the given `email` already exists, it is updated rather than duplicated.

        '
      properties:
        email:
          type: string
          format: email
          description: Contact email address. Used as the unique identifier for find-or-create.
        name:
          type: string
          description: Full name (first and last).
        phone_number:
          type: string
        company_name:
          type: string
          description: Company name. Creates or finds a company record automatically.
        company_id_number:
          type: string
          description: Company tax/ID number (RUT, DNI, etc.), stored with the company
            record.
        tags:
          type: array
          items:
            type: string
          description: Tag names. Replaces all existing tags.
        opt_in:
          type: boolean
          description: Whether the contact accepts marketing emails/newsletters.
        contact_group_id:
          type: integer
          description: Contact group ID to assign this contact to.
        salesperson_id:
          type: string
          description: Shop admin user ID
        source:
          type: string
          enum:
          - signup-form
          - register-form
          - contact-form
          - checkout
          description: Origin of the contact record. Defaults to `API` if omitted
            on create.
        custom_data:
          type: array
          description: Custom field values for this contact.
          items:
            type: object
            properties:
              key:
                type: string
                description: Field key (slug)
              field_id:
                type: integer
                description: Field definition ID (optional alternative to key)
              value:
                description: Field value
    ContactList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - customers
          _embedded:
            type: object
            properties:
              contacts:
                type: array
                items:
                  "$ref": "#/components/schemas/Contact"
    PriceList:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        name:
          type: string
        currency_code:
          type: string
        prices_include_tax:
          type: boolean
        use_price_comparison_for_vol_discounts:
          type: boolean
        tax:
          type: number
        contact_groups_count:
          type: integer
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    PriceListList:
      allOf:
      - "$ref": "#/components/schemas/Pagination"
      - type: object
        properties:
          _links:
            "$ref": "#/components/schemas/HalLinks"
          _class:
            type: array
            items:
              type: string
            example:
            - results
            - priceLists
          _embedded:
            type: object
            properties:
              price_lists:
                type: array
                items:
                  "$ref": "#/components/schemas/PriceList"
    PriceItem:
      type: object
      description: "`id` is the variant ID (`variant_id`) — there is no independent
        price-item ID.\n"
      properties:
        id:
          type: integer
        variant_id:
          type: integer
        product_id:
          type: integer
        name:
          type: string
        sku:
          type: string
        price:
          type: integer
          description: In cents
        price_comparison:
          type: integer
        retail_price:
          type: integer
          description: Original (non-price-list) price, in cents
        cart_quantity_multiple:
          type: integer
        visible:
          type: boolean
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    Theme:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: string
          description: Opaque theme identifier
        linked:
          type: boolean
          description: Whether this is a linked (inherited) theme
        parent_id:
          type:
          - string
          - 'null'
        production:
          type: boolean
          description: "`true` for the live/public theme"
        public:
          type: boolean
          description: Alias for `production`
        accessible:
          type: boolean
        title:
          type: string
        settings:
          type: string
          description: Raw JSON string of theme settings
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
        _embedded:
          type: object
          properties:
            templates:
              type: array
              items:
                "$ref": "#/components/schemas/ThemeTemplate"
            assets:
              type: array
              items:
                "$ref": "#/components/schemas/ThemeAsset"
    ThemeTemplate:
      type: object
      description: There is no `name`/`content_type` field — use `file_name`/`path`.
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: string
        file_name:
          type: string
          example: layout.html
        path:
          type: string
        body:
          type: string
          description: Full template source code
        is_section:
          type: boolean
        schema:
          type: object
          description: Present only when `is_section` is true — the section's configurable
            schema.
        parent_commit:
          type:
          - string
          - 'null'
        updated_on:
          type: string
          format: date-time
    ThemeAsset:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: string
        file_name:
          type: string
          example: logo.png
        content_type:
          type: string
          example: image/png
        file_size:
          type: integer
        digest:
          type: string
        url:
          type: string
          description: CDN URL for this asset
        updated_on:
          type: string
          format: date-time
    Post:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        title:
          type: string
        slug:
          type: string
        body:
          type: string
        description:
          type: string
        short_description:
          type: string
        published:
          type: boolean
          description: There is no `status` field — this is a boolean.
        published_on:
          type: string
          format: date-time
        format:
          type: string
          enum:
          - html
          - markdown
        user_id:
          type: integer
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    Page:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        title:
          type: string
        slug:
          type: string
        body:
          type: string
        description:
          type: string
        visible:
          type: boolean
        format:
          type: string
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    Form:
      type: object
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        title:
          type: string
        slug:
          type: string
        body:
          type: string
        description:
          type: string
        visible:
          type: boolean
        format:
          type: string
        has_address_fields:
          type: boolean
        success_message:
          type: string
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    SubscriptionPurchase:
      type: object
      description: A customer's active subscription to a product (created via a recurring
        order).
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        shop_id:
          type: integer
        contact_id:
          type: integer
        original_order_id:
          type: integer
        product_id:
          type: integer
        product_name:
          type: string
        state:
          type: string
        currency_code:
          type: string
        total_renewal_fee:
          type: integer
          description: In cents
        payment_interval:
          type: string
        total_payments:
          type: integer
        remaining_payments:
          type: integer
        failed_attempts:
          type: integer
        last_payment_on:
          type: string
          format: date
        next_payment_on:
          type: string
          format: date
        start_date:
          type: string
          format: date
        end_date:
          type:
          - string
          - 'null'
          format: date
        cancel_at:
          type:
          - string
          - 'null'
          format: date
        subscription_fee_id:
          type: integer
        renewal_discount:
          type: number
        discount_num_charges:
          type: integer
        payments_to_skip:
          type: integer
        paused_by:
          type: string
        paid_setup_fee:
          type: boolean
        units:
          type: integer
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
    SubscriptionFee:
      type: object
      description: Recurring-payment terms attached to a product or variant.
      properties:
        _links:
          "$ref": "#/components/schemas/HalLinks"
        id:
          type: integer
        seller_id:
          type: integer
        product_id:
          type: integer
        variant_id:
          type: integer
        payment_interval:
          type: string
          enum:
          - 1_week
          - 1_month
          - 3_months
          - 6_months
          - 12_months
        renewal_fee:
          type: integer
          description: In cents
        renewal_fee_price:
          type: integer
          description: In cents
        setup_fee:
          type: integer
          description: In cents
        number_of_payments:
          type: integer
        payment_day:
          type: integer
        by_percentage:
          type: boolean
        allow_non_recurring_payment:
          type: boolean
        min_days_for_cancellation:
          type: integer
        allow_changing_day:
          type: boolean
        extend_on_reenable:
          type: boolean
        on_first_renewal:
          type: string
        created_on:
          type: string
          format: date-time
        updated_on:
          type: string
          format: date-time
paths:
  "/":
    get:
      tags:
      - Root
      summary: API root
      description: |
        Returns the API entry point. Embeds the authenticated account's shops and
        provides navigation links (`_links`) to all v2 resources.

        The `products:list` link is a URI template — expand it with search
        parameters to list products.
      operationId: getRoot
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2"
      responses:
        '200':
          description: API root entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Root"
              example:
                _class:
                - root
                account_id: 10
                account_status: active
                uid: 42
                email: admin@myshop.com
                user_name: admin
                provider: bolder
                scopes: products.read,orders.read,orders.write
                _links:
                  curies:
                  - name: sellers
                    href: https://api.onbolder.com/docs/v2#sellers:{rel}
                    templated: true
                  - name: products
                    href: https://api.onbolder.com/docs/v2#products:{rel}
                    templated: true
                  - name: shops
                    href: https://api.onbolder.com/docs/v2#shops:{rel}
                    templated: true
                  - name: orders
                    href: https://api.onbolder.com/docs/v2#orders:{rel}
                    templated: true
                  - name: customers
                    href: https://api.onbolder.com/docs/v2#customers:{rel}
                    templated: true
                  self:
                    href: https://api.onbolder.com/v2
                  sellers:list:
                    href: https://api.onbolder.com/v2/sellers
                  sellers:show:
                    href: https://api.onbolder.com/v2/sellers/{id}
                    templated: true
                  products:list:
                    href: https://api.onbolder.com/v2/products{?q,status,page,per_page,...}
                    templated: true
                  orders:list:
                    href: https://api.onbolder.com/v2/orders{?shop_id,status,page,...}
                    templated: true
                  customers:list:
                    href: https://api.onbolder.com/v2/customers{?shop_id,q,page,...}
                    templated: true
                _embedded:
                  sellers:
                  - id: 5
                    account_id: 10
                    locale: en
                    currency_code: USD
                    products_count: 42
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/sellers/5
                      shops:show:
                        href: https://api.onbolder.com/v2/shops/1
                      shops:list:
                        href: https://api.onbolder.com/v2/shops?seller_id=5
                  shops:
                  - id: 1
                    name: My Shop
                    subdomain: myshop
                    url: myshop.onbolder.com
                    status: active
        '401':
          description: Invalid or missing token
        '403':
          description: Access denied
  "/sellers":
    get:
      tags:
      - Sellers
      summary: List sellers
      description: |
        Returns all sellers belonging to the authenticated account.
        Results are automatically scoped to the account associated with the token.
      operationId: listSellers
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/sellers"
      responses:
        '200':
          description: List of sellers
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SellerList"
              example:
                _class:
                - results
                - sellers
                total_items: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/sellers
                _embedded:
                  items:
                  - id: 5
                    account_id: 10
                    locale: en
                    currency_code: USD
                    provides_shipping: true
                    provides_pickup: true
                    prices_include_tax: true
                    tax_rate: 0.19
                    tax_name: VAT
                    products_count: 42
                    created_on: '2023-01-15T10:00:00Z'
                    updated_on: '2024-03-20T14:30:00Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/sellers/5
                      shops:show:
                        href: https://api.onbolder.com/v2/shops/1
                      shops:list:
                        href: https://api.onbolder.com/v2/shops?seller_id=5
                      products:list:
                        href: https://api.onbolder.com/v2/products{?seller_ids,...}
                        templated: true
        '401':
          description: Invalid or missing token
        '403':
          description: Access denied
  "/sellers/{id}":
    get:
      tags:
      - Sellers
      summary: Get a seller
      description: Returns a single seller by ID. Sellers are account-scoped.
      operationId: getSeller
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/sellers/5"
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Seller ID
        example: 5
      responses:
        '200':
          description: Seller entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Seller"
              example:
                id: 5
                account_id: 10
                locale: en
                currency_code: USD
                provides_shipping: true
                provides_pickup: true
                prices_include_tax: true
                tax_rate: 0.19
                tax_name: VAT
                products_count: 42
                created_on: '2023-01-15T10:00:00Z'
                updated_on: '2024-03-20T14:30:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/sellers/5
                  shops:show:
                    href: https://api.onbolder.com/v2/shops/1
                    title: Main shop
                  shops:list:
                    href: https://api.onbolder.com/v2/shops?seller_id=5
                    title: All shops owned by this seller
                  products:list:
                    href: https://api.onbolder.com/v2/products{?seller_ids,...}
                    templated: true
        '401':
          description: Invalid or missing token
        '403':
          description: Access denied
        '404':
          description: Seller not found
  "/vendors":
    get:
      tags:
      - Catalog
      summary: List vendors
      description: |
        Returns vendors across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listVendors
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
        example: 1
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
        example: 5
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 25
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          # List all vendors for account
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/vendors"

          # Filter by shop
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/vendors?shop_id=1"

          # Filter by seller
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/vendors?seller_id=5"
      responses:
        '200':
          description: Vendors list
          content:
            application/json:
              example:
                _class:
                - results
                - vendors
                total_items: 2
                _embedded:
                  items:
                  - id: 1
                    name: Acme Corp
                    slug: acme-corp
                    products_count: 15
  "/product_sets":
    get:
      tags:
      - Catalog
      summary: List product sets
      description: |
        Returns product sets across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listProductSets
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 25
      responses:
        '200':
          description: Product sets list
  "/product_groups":
    get:
      tags:
      - Catalog
      summary: List product groups
      description: |
        Returns aggregated product groups (collections, product types, vendors, tags) — a
        cross-collection discovery search, not CRUD on a real "product group" table.

        Filtering:

        - **`shop_id`** — scopes to that shop's seller.
        - **`seller_id`** — scopes to that seller.
        - Neither given — account-scoped, across all of the token's sellers.

        `q` (substring match on name/slug) and `id` + `group_type` (direct lookup of a single
        group; `group_type` is one of `collection`, `product_type`, `vendor`, `tag`) work the same
        way under all three filters. Tags are only included in the `q` and `id`/`group_type`
        results — the unfiltered/default listing (most-recently-updated) never includes tags.
        Not paginated — only `per_page` applies (default 10, max 25); there is no `page` param.
      operationId: listProductGroups
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID.
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID. Ignored if `shop_id` is present.
      - name: q
        in: query
        schema:
          type: string
        description: Substring search on name/slug (includes tags).
      - name: id
        in: query
        schema:
          type: integer
        description: Look up a single group by id. Requires `group_type` (includes
          tags).
      - name: group_type
        in: query
        schema:
          type: string
          enum:
          - collection
          - product_type
          - vendor
          - tag
        description: Required together with `id`.
      - name: per_page
        in: query
        schema:
          type: integer
          default: 10
          maximum: 25
      responses:
        '200':
          description: Product groups list
          content:
            application/json:
              example:
                _class:
                - results
                - product_groups
                total_items: 3
                per_page: 10
                page: 1
                _embedded:
                  items:
                  - type: collection
                    id: 1
                    name: Summer
                    slug: summer
                    products_count: 12
                    updated_at: '2026-07-01T10:00:00Z'
                  - type: product_type
                    id: 2
                    name: Clothing
                    slug: clothing
                    products_count: 45
                    updated_at: '2026-06-15T09:30:00Z'
  "/customer_tags":
    get:
      tags:
      - Customers
      summary: List customer tags
      description: |
        Returns customer tags across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listCustomerTags
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Customer tags list
  "/order_tags":
    get:
      tags:
      - Orders
      summary: List order tags
      description: |
        Returns order tags across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listOrderTagsFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Order tags list
  "/product_tags":
    get:
      tags:
      - Products
      summary: List product tags
      description: |
        Returns product tags across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listProductTagsFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Product tags list
  "/events":
    get:
      tags:
      - Shops
      summary: List events
      description: |
        Returns events across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listEventsFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      - name: topic
        in: query
        schema:
          type: string
        description: Filter by event topic
      responses:
        '200':
          description: Events list
  "/actions/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 5
  "/exchange_rates":
    get:
      tags:
      - Shops
      summary: List exchange rates
      description: |
        Returns exchange rates across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listExchangeRatesFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      responses:
        '200':
          description: Exchange rates list
  "/payment_methods":
    get:
      tags:
      - Shops
      summary: List payment methods
      description: |
        Returns payment methods across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listPaymentMethodsFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: Payment methods list
  "/webhooks":
    get:
      tags:
      - Webhooks
      summary: List webhooks
      operationId: listWebhooksFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      - name: status
        in: query
        schema:
          type: string
        description: Filter by status (active, failed_activation, failed, disabled)
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          # List all webhooks for account
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/webhooks"

          # Filter by shop
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/webhooks?shop_id=1"
      description: |
        Returns webhooks across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.

        ## Delivery payload guarantees

        Every order-related event payload (`orders.created`, `orders.updated`,
        `orders.updated.*`) is built from the same serializer and always includes
        both `id` (numeric order id) and `code` (the order's public-facing
        reference/permalink) — neither is ever omitted.

        ## Retries and automatic disabling

        Two independent counters are involved — don't confuse them:

        - **Per-event retries (up to 10, over ~24h)**: each individual event
          delivery (e.g. one order's `orders.updated`) is retried by the
          delivery worker up to 10 times with growing backoff, spread across
          roughly a day (a few minutes after the 1st failure, up to ~24h
          after it by the 10th and final attempt) — this gives a
          temporarily-down endpoint a full day to recover before this
          specific event delivery is given up on.
        - **Subscription-wide `error_count` (disables at 100)**: every
          failed attempt across *all* events (including each of the 10
          per-event retries above) increments `error_count` on the
          subscription. Reaching 100 sets `status` to `failed` and stops all
          further delivery — in practice that means roughly 10 different
          order events each exhausting their full retry chain with no
          successful delivery in between, since **any single successful
          delivery resets `error_count` to 0**. There's no fixed time bound
          on this: it depends on how many order events fire while your
          endpoint is down.

        Delivery outcomes are tracked on the subscription independent of the
        worker retries:

        - A `410 Gone` response from your endpoint immediately sets the
          subscription's `status` to `disabled` — no further attempts are made,
          regardless of `error_count`.
        - Any other non-2xx response (or timeout) increments `error_count`.
          Timeouts and a handful of transient upstream errors (502/512/523) don't
          count toward the disable threshold; other non-2xx responses do.
        - A `failed` (or `disabled`) subscription can be re-enabled at any time via
          `PUT /webhooks/{id}/reactivate`, which resets the error count.
      responses:
        '200':
          description: Webhooks list
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/WebhookList"
    post:
      tags:
      - Webhooks
      summary: Create a webhook
      description: "`shop_id` is required when creating via this flat endpoint. Alternatively,
        use `POST /sellers/{seller_id}/webhooks`.\n"
      operationId: createWebhookFlattened
      parameters:
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/WebhookInput"
            example:
              topic: orders.created
              url: https://myapp.com/webhook
      responses:
        '201':
          description: Webhook created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Webhook"
        '403':
          description: Token lacks the scope required to subscribe to this topic
  "/webhooks/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Webhooks
      summary: Get a webhook
      operationId: getWebhookFlattened
      responses:
        '200':
          description: Webhook details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Webhook"
    delete:
      tags:
      - Webhooks
      summary: Delete a webhook
      operationId: deleteWebhookFlattened
      responses:
        '204':
          description: Webhook deleted
  "/webhooks/{id}/reactivate":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    put:
      tags:
      - Webhooks
      summary: Reactivate a failed webhook
      operationId: reactivateWebhookFlattened
      responses:
        '200':
          description: Webhook reactivated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Webhook"
  "/webhooks/{id}/history":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Webhooks
      summary: Get webhook delivery history
      operationId: webhookHistoryFlattened
      responses:
        '200':
          description: Delivery history
  "/sellers/{seller_id}/webhooks":
    parameters:
    - name: seller_id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Webhooks
      summary: List webhooks for a seller
      description: Seller-nested equivalent of `GET /webhooks?seller_id=X`.
      operationId: listSellerWebhooks
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/sellers/5/webhooks"
      responses:
        '200':
          description: Webhooks list
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/WebhookList"
    post:
      tags:
      - Webhooks
      summary: Create a webhook for a seller
      operationId: createSellerWebhook
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"topic":"orders.created","url":"https://myapp.com/webhook"}' \
            "https://api.onbolder.com/v2/sellers/5/webhooks"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/WebhookInput"
            example:
              topic: orders.created
              url: https://myapp.com/webhook
      responses:
        '201':
          description: Webhook created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Webhook"
              example:
                id: 101
                channel_id: 5
                topic: orders.created
                url: https://myapp.com/webhook
                status: pending
                notify_origin: false
                _links:
                  self:
                    href: https://api.onbolder.com/v2/webhooks/101
        '403':
          description: Token lacks the scope required to subscribe to this topic
  "/themes":
    get:
      tags:
      - Store
      summary: List themes
      description: 'Returns themes across the account.

        '
      operationId: listThemesFlattened
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: Themes list
  "/shipping_tables":
    get:
      tags:
      - Shops
      summary: List shipping tables
      description: |
        Returns shipping tables across the account.
        Optionally filter by `shop_id` or `seller_id` query parameter.
      operationId: listShippingTablesFlattened
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: Filter by shop ID
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter by seller ID
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: Shipping tables list
  "/shipping_tables/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Shops
      summary: Get a shipping table
      operationId: getShippingTableFlattened
      responses:
        '200':
          description: Shipping table details
  "/collections":
    get:
      tags:
      - Collections
      summary: List collections
      description: Returns collections for a shop. `shop_id` is required.
      operationId: listCollections
      parameters:
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
        description: ID of the shop whose collections to list.
        example: 1
      - name: page
        in: query
        schema:
          type: integer
          default: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
        example: 20
      responses:
        '200':
          description: Paginated collections list
          content:
            application/json:
              example:
                _class:
                - results
                - collections
                total_items: 2
                per_page: 20
                page: 1
                _embedded:
                  items:
                  - id: 5
                    title: Summer Collection
                    slug: summer
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/collections/5
  "/collections/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      description: Collection ID
      example: 5
    get:
      tags:
      - Collections
      summary: Get a collection
      responses:
        '200':
          description: Collection details
          content:
            application/json:
              example:
                id: 5
                title: Summer Collection
                slug: summer
    delete:
      tags:
      - Collections
      summary: Delete a collection
      description: Requires `products.write` scope on the collection's seller.
      operationId: deleteCollection
      responses:
        '204':
          description: Collection deleted
        '403':
          description: Access denied
  "/product_types":
    get:
      tags:
      - Product Types
      summary: List product types
      description: Returns product types for a shop. `shop_id` is required.
      operationId: listProductTypes
      parameters:
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
        example: 1
      responses:
        '200':
          description: Product types list
          content:
            application/json:
              example:
                _class:
                - results
                - productTypes
                total_items: 3
                _embedded:
                  items:
                  - id: 1
                    name: Clothing
                    slug: clothing
    post:
      tags:
      - Product Types
      summary: Create a product type
      operationId: createProductType
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - shop_id
              properties:
                shop_id:
                  type: integer
                name:
                  type: string
                slug:
                  type: string
                description:
                  type: string
                cart_quantity_multiple:
                  type: integer
                unit_of_measure:
                  type: string
                unit_display_quantity:
                  type: integer
            example:
              shop_id: 1
              name: Electronics
              slug: electronics
      responses:
        '201':
          description: Product type created
          content:
            application/json:
              example:
                id: 2
                slug: electronics
                name: Electronics
                cart_max_quantity_per_product:
                cart_quantity_multiple:
  "/product_types/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    put:
      tags:
      - Product Types
      summary: Update or upsert a product type
      description: 'If no product type with this `id` exists for the account''s seller,
        one is created instead (upsert), returning 201.

        '
      operationId: updateProductType
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                slug:
                  type: string
                cart_quantity_multiple:
                  type: integer
                unit_of_measure:
                  type: string
            example:
              name: Electronics Updated
      responses:
        '200':
          description: Product type updated
        '201':
          description: Product type did not exist — created instead
  "/products":
    get:
      tags:
      - Products
      summary: List products
      description: |
        Returns products scoped to the authenticated account.

        **Scope behaviour:**
        - Tokens with `products.read` (or `products.write`) scoped to a seller/account:
          automatically scoped to the account's own sellers — no extra params needed.
        - Tokens without an account/seller scope: `shop_subdomains` is **required**
          (prevents enumeration).
      operationId: listProducts
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          # List all visible products (account-scoped token)
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/products"

          # Search with filters
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/products?q=shirt&status=visible&per_page=50"
      parameters:
      - name: q
        in: query
        schema:
          type: string
        description: Full-text search query across product name and description.
        example: summer dress
      - name: status
        in: query
        schema:
          type: string
          enum:
          - visible
          - hidden
          - all
          default: visible
        description: Filter by visibility. Account-scoped tokens may use `hidden`
          or `all`.
        example: visible
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      - name: ids
        in: query
        schema:
          type: string
        description: Comma-separated product IDs.
        example: '101,102,103'
      - name: collections
        in: query
        schema:
          type: string
        description: Comma-separated collection slugs. Returns products belonging
          to all listed collections.
        example: summer,featured
      - name: tags
        in: query
        schema:
          type: string
        description: Comma-separated product tags to filter by.
        example: sale,new-arrival
      - name: sku
        in: query
        schema:
          type: string
        description: Filter by exact variant SKU.
        example: SKU-001
      - name: product_type_id
        in: query
        schema:
          type: integer
        description: Filter by product type ID.
        example: 5
      - name: price_gte
        in: query
        schema:
          type: number
        description: Minimum sale price (inclusive).
        example: 10.0
      - name: price_lte
        in: query
        schema:
          type: number
        description: Maximum sale price (inclusive).
        example: 100.0
      - name: stock_gte
        in: query
        schema:
          type: integer
        description: Minimum stock quantity (inclusive). Use `1` to exclude out-of-stock
          items.
        example: 1
      - name: stock_lte
        in: query
        schema:
          type: integer
        description: Maximum stock quantity (inclusive).
        example: 50
      - name: created_on_gte
        in: query
        schema:
          type: string
          format: date-time
        description: Products created on or after this ISO 8601 timestamp.
        example: '2024-01-01T00:00:00Z'
      - name: created_on_lte
        in: query
        schema:
          type: string
          format: date-time
        description: Products created on or before this ISO 8601 timestamp.
        example: '2024-12-31T23:59:59Z'
      - name: updated_on_gte
        in: query
        schema:
          type: string
          format: date-time
        description: Products updated on or after this ISO 8601 timestamp.
        example: '2024-06-01T00:00:00Z'
      - name: updated_on_lte
        in: query
        schema:
          type: string
          format: date-time
        description: Products updated on or before this ISO 8601 timestamp.
        example: '2024-12-31T23:59:59Z'
      - name: sort
        in: query
        schema:
          type: string
          enum:
          - created_on_asc
          - created_on_desc
          - updated_on_asc
          - updated_on_desc
          - price_asc
          - price_desc
          - model_asc
          - model_desc
          default: created_on_desc
        description: Sort order for results.
        example: created_on_desc
      - name: currency
        in: query
        schema:
          type: string
        description: ISO 4217 currency code to filter by.
        example: USD
      - name: price_list_id
        in: query
        schema:
          type: integer
        description: Return prices from this price list instead of the default.
        example: 7
      - name: shop_subdomains
        in: query
        schema:
          type: string
        description: |
          Comma-separated shop subdomains.
          **Required** for god-level tokens. Ignored for account-scoped tokens.
        example: myshop
      - name: suggest
        in: query
        schema:
          type: boolean
          default: false
        description: Return lightweight suggestion objects for autocomplete (fewer
          fields rendered).
        example: false
      - name: exact
        in: query
        schema:
          type: boolean
          default: false
        description: When `true`, `q` is matched exactly rather than as a prefix/fuzzy
          query.
        example: false
      responses:
        '200':
          description: Paginated list of products
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ProductList"
              example:
                _class:
                - results
                - products
                total_items: 2
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/products
                  shops:show:
                    href: https://api.onbolder.com/v2/shops/1
                _embedded:
                  items:
                  - id: 101
                    name: Summer Dress
                    slug: summer-dress
                    status: visible
                    price: 49.99
                    regular_price: 69.99
                    currency: USD
                    created_on: '2024-03-15T10:00:00Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/products/101
                      products:update:
                        href: https://api.onbolder.com/v2/products/101
                        method: put
                      products:delete:
                        href: https://api.onbolder.com/v2/products/101
                        method: delete
                  - id: 102
                    name: Linen Shirt
                    slug: linen-shirt
                    status: visible
                    price: 35.0
                    regular_price: 35.0
                    currency: USD
                    created_on: '2024-02-10T08:30:00Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/products/102
        '401':
          description: Invalid or missing token
        '403':
          description: Access denied
        '422':
          description: Missing required parameter (e.g. `shop_subdomains` for tokens
            without an account/seller scope)
    post:
      tags:
      - Products
      summary: Create a product
      description: Creates a product in the specified shop. `shop_id` must be a shop
        belonging to the authenticated account.
      operationId: createProduct
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"shop_id":1,"name":"Organic Cotton Tee","status":"visible","sale_price":2999,"regular_price":3999}' \
            "https://api.onbolder.com/v2/products"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ProductInput"
            example:
              shop_id: 1
              name: Organic Cotton Tee
              slug: organic-cotton-tee
              status: visible
              sale_price: 2999
              regular_price: 3999
      responses:
        '201':
          description: Product created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Product"
              example:
                id: 103
                name: Organic Cotton Tee
                slug: organic-cotton-tee
                status: visible
                price: 2999
                regular_price: 3999
                currency_code: USD
                created_on: '2024-06-20T14:22:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/products/103
                  products:update:
                    href: https://api.onbolder.com/v2/products/103
                    method: put
                  products:delete:
                    href: https://api.onbolder.com/v2/products/103
                    method: delete
        '422':
          description: Validation error or missing shop_id
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ValidationErrors"
        '403':
          description: Not authorized to create products in this shop
  "/products/updaters":
    post:
      tags:
      - Products
      summary: Batch update products
      description: 'Updates multiple **existing** products in a single request (does
        not create new ones). `tags` is the only field that supports add/remove batch
        operations — there is no way to bulk-set `status`, price, or other fields
        through this endpoint.

        '
      operationId: batchUpdateProducts
      parameters:
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - ids
              properties:
                ids:
                  type: array
                  items:
                    type: integer
                additions:
                  type: object
                  properties:
                    tags:
                      type: array
                      items:
                        type: string
                removals:
                  type: object
                  properties:
                    tags:
                      type: array
                      items:
                        type: string
            example:
              ids:
              - 101
              - 102
              additions:
                tags:
                - clearance
      responses:
        '201':
          description: Products processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      valid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            title:
                              type: string
                      invalid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            title:
                              type: string
              example:
                ids:
                - 101
                - 102
                additions:
                  tags:
                  - clearance
                _embedded:
                  valid_items:
                  - id: 101
                    name: Product A
                  invalid_items: []
        '422':
          description: Validation error (e.g. missing `ids`, or neither `additions`/`removals`
            given)
  "/products/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Product ID or slug
    get:
      tags:
      - Products
      summary: Get a product
      operationId: getProduct
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/products/101"
      responses:
        '200':
          description: Product entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Product"
              example:
                id: 101
                name: Wireless Headphones
                slug: wireless-headphones
                status: visible
                regular_price: 11999
                sale_price: 8999
                price: 8999
                currency_code: USD
                description: Premium wireless headphones with noise cancellation.
                tags:
                - electronics
                - audio
                _links:
                  self:
                    href: https://api.onbolder.com/v2/products/101
                  products:update:
                    href: https://api.onbolder.com/v2/products/101
                    method: put
                  products:delete:
                    href: https://api.onbolder.com/v2/products/101
                    method: delete
        '404':
          description: Product not found
        '403':
          description: Access denied (hidden product of another account)
    put:
      tags:
      - Products
      summary: Update a product
      description: |
        Updates the product. If a product with the given ID/slug does not exist and
        `dont_create` is not set, a new product will be created (upsert).
      operationId: updateProduct
      parameters:
      - name: dont_create
        in: query
        schema:
          type: boolean
        description: When true, do not upsert a new product if the ID/slug is not
          found (404 instead).
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X PUT \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"status":"hidden","sale_price":2499}' \
            "https://api.onbolder.com/v2/products/101"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ProductInput"
            example:
              name: Wireless Headphones Pro
              regular_price: 12999
              sale_price: 9999
              status: visible
              description: Updated premium wireless headphones.
              tags:
              - electronics
              - audio
              - wireless
      responses:
        '200':
          description: Product updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Product"
              example:
                id: 101
                name: Wireless Headphones Pro
                slug: wireless-headphones-pro
                status: visible
                price: 9999
                regular_price: 12999
                currency_code: USD
                _links:
                  self:
                    href: https://api.onbolder.com/v2/products/101
        '201':
          description: Product created (upsert)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Product"
        '403':
          description: Access denied
    delete:
      tags:
      - Products
      summary: Delete a product
      operationId: deleteProduct
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X DELETE \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/products/101"
      responses:
        '204':
          description: Product deleted
        '403':
          description: Access denied
        '404':
          description: Product not found
  "/products/{id}/price_items":
    get:
      tags:
      - Products
      summary: List product price items
      description: Returns price items for a specific product.
      operationId: getProductPriceItems
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Product ID or slug
      responses:
        '200':
          description: Price items list
          content:
            application/json:
              example:
                _class:
                - results
                - priceItems
                total_items: 3
        '404':
          description: Product not found
  "/products/{product_id}/variants":
    get:
      tags:
      - Variants
      summary: List variants
      description: Returns variants for a product.
      operationId: listVariants
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/products/101/variants"
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
        description: Product ID or slug
        example: '101'
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
      responses:
        '200':
          description: Paginated variants list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          "$ref": "#/components/schemas/Variant"
              example:
                _class:
                - results
                - variants
                total_items: 3
                per_page: 20
                page: 1
                _embedded:
                  items:
                  - id: 201
                    product_id: 101
                    name: Small
                    sku: WH-001-S
                    option1: Small
                    currency_code: USD
                    regular_price: 8999
                    price: 8999
                    online_stock: 10
    post:
      tags:
      - Variants
      summary: Create a variant
      description: Creates a new variant for a product.
      operationId: createVariant
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: integer
                  description: Pre-assign a specific variant ID (rarely used; usually
                    omitted).
                option1:
                  type: string
                option2:
                  type: string
                option3:
                  type: string
                option_values:
                  type: object
                  description: Custom variant option values, keyed by `option1`/`option2`/`option3`.
                option_asset_ids:
                  type: object
                  description: Asset IDs to associate with each option value.
                product_asset_ids:
                  type: array
                  items:
                    type: integer
                  description: Product image asset IDs to associate with this variant.
                sku:
                  type: string
                barcode_number:
                  type: string
                regular_price:
                  type: integer
                  description: In cents. Required if the product has per-variant prices.
                sale_price:
                  type: integer
                  description: In cents
                cost:
                  type: integer
                weight_in_grams:
                  type: integer
                unit_quantity:
                  type: number
                color:
                  type: string
                available_if_no_stock:
                  type: boolean
                  default: false
                online_stock:
                  type: integer
                status:
                  type: string
                  enum:
                  - visible
                  - hidden
                stocks:
                  type: array
                  items:
                    type: object
                    properties:
                      location_id:
                        type: integer
                      units:
                        type: integer
              example:
                option1: Large
                sku: WH-001-L
                regular_price: 8999
                stocks:
                - location_id: 1
                  units: 15
      responses:
        '201':
          description: Variant created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Variant"
              example:
                id: 202
                product_id: 101
                name: Large
                sku: WH-001-L
                regular_price: 8999
                price: 8999
  "/products/{product_id}/variants/{id}":
    parameters:
    - name: product_id
      in: path
      required: true
      schema:
        type: string
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Variant ID or SKU
    get:
      tags:
      - Variants
      summary: Get a variant
      operationId: getVariant
      responses:
        '200':
          description: Variant details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Variant"
              example:
                id: 201
                product_id: 101
                name: Small
                sku: WH-001-S
                regular_price: 8999
                price: 8999
                online_stock: 10
        '404':
          description: Variant not found
    put:
      tags:
      - Variants
      summary: Update a variant
      description: 'Variant names are set via `option1`/`option2`/`option3`. On update,
        `stocks` accepts `available_units`/`total_units` in addition to `location_id`.

        '
      operationId: updateVariant
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                sku:
                  type: string
                barcode_number:
                  type: string
                option1:
                  type: string
                option2:
                  type: string
                option3:
                  type: string
                option_values:
                  type: object
                  description: Custom variant option values, keyed by `option1`/`option2`/`option3`.
                option_asset_ids:
                  type: object
                  description: Asset IDs to associate with each option value.
                product_asset_ids:
                  type: array
                  items:
                    type: integer
                  description: Product image asset IDs to associate with this variant.
                color:
                  type: string
                regular_price:
                  type: integer
                sale_price:
                  type: integer
                cost:
                  type: integer
                  description: Cost in cents (for margin reporting, not shown to buyers).
                status:
                  type: string
                  enum:
                  - visible
                  - hidden
                available_if_no_stock:
                  type: boolean
                weight_in_grams:
                  type: integer
                unit_quantity:
                  type: number
                online_stock:
                  type: integer
                stocks:
                  type: array
                  items:
                    type: object
                    properties:
                      location_id:
                        type: integer
                      available_units:
                        type: integer
                      total_units:
                        type: integer
                      allows_negative:
                        type: boolean
            example:
              regular_price: 7999
      responses:
        '200':
          description: Variant updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Variant"
    delete:
      tags:
      - Variants
      summary: Delete a variant
      operationId: deleteVariant
      responses:
        '204':
          description: Variant deleted
  "/products/{product_id}/variants/updaters":
    post:
      tags:
      - Variants
      summary: Batch update variants
      description: 'Updates multiple **existing** variants in a single request (does
        not create new variants). Each entry in `batch` must include `id` (numeric
        variant ID or SKU) to match an existing variant; unmatched IDs are silently
        skipped. Stock fields use `available_units`/`total_units` (`units` is deprecated
        but still accepted).

        '
      operationId: batchUpdateVariants
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - batch
              properties:
                batch:
                  type: array
                  items:
                    type: object
                    required:
                    - id
                    properties:
                      id:
                        type: string
                        description: Existing variant ID or SKU
                      title:
                        type: string
                        description: Accepted (unlike the standalone update endpoint,
                          which ignores it)
                      sku:
                        type: string
                      regular_price:
                        type: integer
                      sale_price:
                        type: integer
                      status:
                        type: string
                        enum:
                        - visible
                        - hidden
                      stocks:
                        type: array
                        items:
                          type: object
                          properties:
                            location_id:
                              type: integer
                            available_units:
                              type: integer
                            total_units:
                              type: integer
            example:
              batch:
              - id: '201'
                title: Small
                regular_price: 7999
              - id: '202'
                title: Large
                regular_price: 8999
      responses:
        '201':
          description: Variants processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      valid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            title:
                              type: string
                      invalid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            title:
                              type: string
  "/products/{product_id}/assets":
    post:
      tags:
      - Products
      summary: Create a product asset
      operationId: createProductAsset
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                asset_type:
                  type: string
                src:
                  type: string
      responses:
        '201':
          description: Asset created
  "/products/{product_id}/assets/{id}":
    delete:
      tags:
      - Products
      summary: Delete a product asset
      operationId: deleteProductAsset
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '204':
          description: Asset deleted
  "/products/{product_id}/relations":
    post:
      tags:
      - Products
      summary: Create a product relation
      operationId: createProductRelation
      parameters:
      - name: product_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                related_product_id:
                  type: integer
                relation_type:
                  type: string
      responses:
        '201':
          description: Relation created
  "/shops":
    get:
      tags:
      - Shops
      summary: List shops
      description: 'Returns shops belonging to the authenticated account.

        '
      operationId: listShops
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops"
      parameters:
      - name: q
        in: query
        schema:
          type: string
        description: Search shops by name or subdomain.
        example: mystore
      - name: seller_id
        in: query
        schema:
          type: integer
        description: 'Filter to shops owned by a specific seller. Useful for enumerating
          all shops of a seller that owns more than one — see the seller''s `shops:list`
          link.

          '
        example: 5
      - name: status
        in: query
        schema:
          type: string
          enum:
          - active
          - inactive
          - all
          default: all
        description: Filter by shop status.
        example: active
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      responses:
        '200':
          description: Paginated list of shops
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ShopList"
              example:
                _class:
                - results
                - shops
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/shops
                _embedded:
                  items:
                  - id: 1
                    name: My Store
                    subdomain: mystore
                    url: https://mystore.onbolder.com
                    status: active
                    currency: USD
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/shops/1
  "/shops/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Shops
      summary: Get a shop
      operationId: getShop
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1"
      responses:
        '200':
          description: Shop entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Shop"
              example:
                id: 1
                seller_id: 5
                account_id: 10
                name: My Store
                subdomain: mystore
                url: mystore.onbolder.com
                locale: en
                currency_code: USD
                _links:
                  self:
                    href: https://api.onbolder.com/v2/shops/1
                  products:list:
                    href: https://api.onbolder.com/v2/products?shop_id=1
                  orders:list:
                    href: https://api.onbolder.com/v2/orders?shop_id=1
                  customers:list:
                    href: https://api.onbolder.com/v2/customers?shop_id=1
                  hub:subscriptions:
                    href: https://api.onbolder.com/v2/sellers/5/webhooks
        '403':
          description: Access denied
        '404':
          description: Shop not found
  "/shops/{id}/settings":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Shops
      summary: Get shop settings
      operationId: getShopSettings
      responses:
        '200':
          description: Shop with settings
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Shop"
              example:
                id: 1
                name: My Store
                subdomain: mystore
                currency: USD
                country: US
                tax_rate: 0.21
                shipping_zones:
                - domestic
                - international
                payment_methods:
                - credit_card
                - paypal
                _links:
                  self:
                    href: https://api.onbolder.com/v2/shops/1/settings
  "/shops/{id}/themes":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      description: Shop ID
      example: 1
    get:
      tags:
      - Themes
      summary: List themes for a shop
      description: |
        Returns the theme navigation document for the shop — links to the current
        production theme, the dev theme (if any), and the action to create a new
        dev theme. Requires `store.read` scope and ownership of the shop.
      operationId: listShopThemes
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/themes"
      responses:
        '200':
          description: Themes navigation document
          content:
            application/json:
              example:
                _class:
                - results
                - themes
                _links:
                  self:
                    href: https://api.onbolder.com/v2/shops/1/themes
                  store:theme:
                    href: https://api.onbolder.com/v2/themes/abc123
                    title: Current theme
                  store:create_dev_theme:
                    href: https://api.onbolder.com/v2/shops/1/themes
                    method: post
                  store:dev_theme:
                    href: https://api.onbolder.com/v2/themes/def456
                    title: Dev theme
        '401':
          description: Unauthorized
        '403':
          description: Access denied
    post:
      tags:
      - Themes
      summary: Create a dev theme
      description: |
        Creates a new development theme by cloning the current production theme.
        If a dev theme already exists it is returned unchanged.
      operationId: createShopTheme
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: Optional title for the new dev theme
            example:
              title: My Dev Theme
      responses:
        '201':
          description: Dev theme created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Theme"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
  "/shops/{shop_id}/admins":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List shop admins
      description: Returns admin users for the given shop.
      operationId: listShopAdmins
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/admins"
      responses:
        '200':
          description: List of admins
          content:
            application/json:
              example:
                _class:
                - results
                - admins
                total_items: 2
                _embedded:
                  items:
                  - id: 10
                    email: admin@myshop.com
                    name: Admin User
                    role: owner
                    last_logged_on: '2024-06-20T14:00:00Z'
        '403':
          description: Access denied
  "/shops/{shop_id}/admins/{id}":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 10
    get:
      tags:
      - Shops
      summary: Get a shop admin
      operationId: getShopAdmin
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/admins/10"
      responses:
        '200':
          description: Admin entity
          content:
            application/json:
              example:
                id: 10
                email: admin@myshop.com
                name: Admin User
                role: owner
                last_logged_on: '2024-06-20T14:00:00Z'
        '404':
          description: Admin not found
  "/shops/{shop_id}/events":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List shop events
      description: Returns the event log for this shop (webhook delivery history,
        order events, etc).
      operationId: listShopEvents
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/events"
      parameters:
      - name: topic
        in: query
        schema:
          type: string
        description: Filter by event topic (e.g. `orders.created`).
        example: orders.created
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
      responses:
        '200':
          description: Paginated event list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          type: object
                          description: 'There is no `id` field — `sequence` is the
                            event''s identifier.

                            '
                          properties:
                            sequence:
                              type: integer
                            type:
                              type: string
                            topic:
                              type: string
                            item_type:
                              type: string
                            item_id:
                              type: integer
                            item_name:
                              type: string
                            scope:
                              type: string
                            payload:
                              type: object
                            user_id:
                              type: integer
                            user_name:
                              type: string
                            app_name:
                              type: string
                            created_on:
                              type: string
                              format: date-time
              example:
                _class:
                - results
                - events
                total_items: 42
                _embedded:
                  items:
                  - sequence: 1001
                    type: order
                    topic: orders.created
                    item_type: Order
                    item_id: 5001
                    item_name: ORD-5001
                    scope: shop
                    payload: {}
                    created_on: '2024-06-15T09:45:00Z'
  "/shops/{shop_id}/payment_methods":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List payment methods
      description: Returns the payment methods configured for the shop.
      operationId: listPaymentMethods
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/payment_methods"
      responses:
        '200':
          description: Payment methods list
          content:
            application/json:
              example:
                _class:
                - results
                - paymentMethods
                total_items: 2
                _embedded:
                  items:
                  - id: 3
                    type: webpay_rest_payment_method
                    title: Webpay Plus (Rest)
                    active: true
                  - id: 7
                    type: bank_transfer_payment_method
                    title: Transferencia bancaria
                    active: true
  "/shops/{shop_id}/exchange_rates":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List exchange rates
      description: Returns current USD exchange rates for supported currencies.
      operationId: listExchangeRates
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/exchange_rates"
      responses:
        '200':
          description: Exchange rates
          content:
            application/json:
              example:
                _class:
                - results
                - exchangeRates
                _embedded:
                  items:
                  - from: USD
                    to: CLP
                    rate: 950.5
                    updated_at: '2024-06-15T00:00:00Z'
                  - from: USD
                    to: ARS
                    rate: 890.0
                    updated_at: '2024-06-15T00:00:00Z'
  "/shops/{shop_id}/order_tags":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List order tags
      description: Returns all tags currently applied to orders in this shop.
      operationId: listOrderTags
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/order_tags"
      responses:
        '200':
          description: Order tags list
          content:
            application/json:
              example:
                _class:
                - results
                - tags
                _embedded:
                  items:
                  - name: urgent
                  - name: gift
  "/shops/{shop_id}/customer_tags":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Customers
      summary: List customer tags
      description: Returns all tags currently applied to customers in this shop.
      operationId: listContactTags
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/customer_tags"
      responses:
        '200':
          description: Customer tags list
          content:
            application/json:
              example:
                _class:
                - results
                - tags
                _embedded:
                  items:
                  - name: vip
                  - name: newsletter
  "/shops/{shop_id}/product_tags":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Products
      summary: List product tags
      description: Returns all tags currently applied to products in this shop.
      operationId: listProductTags
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/product_tags"
      responses:
        '200':
          description: Product tags list
          content:
            application/json:
              example:
                _class:
                - results
                - tags
                _embedded:
                  items:
                  - name: sale
                  - name: new-arrival
                  - name: featured
  "/shops/{shop_id}/vendors":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Products
      summary: List vendors
      description: Returns all vendor names present in the shop's product catalog.
      operationId: listShopVendors
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/vendors"
      responses:
        '200':
          description: Vendors list
          content:
            application/json:
              example:
                _class:
                - results
                - vendors
                _embedded:
                  items:
                  - name: Acme Corp
                  - name: Widget Co
  "/shops/{shop_id}/custom_product_attributes":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Products
      summary: List custom product attributes
      description: Returns all unique custom attribute names used across products
        in this shop.
      operationId: listCustomProductAttributes
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/custom_product_attributes"
      responses:
        '200':
          description: Custom attributes list
          content:
            application/json:
              example:
                _class:
                - results
                - customAttributes
                _embedded:
                  items:
                  - name: material
                  - name: origin
  "/shops/{shop_id}/product_sets":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Products
      summary: List product sets
      description: Returns product sets (dynamic rule-based groupings) for the shop.
      operationId: listShopProductSets
      responses:
        '200':
          description: Product sets list
          content:
            application/json:
              example:
                _class:
                - results
                - productSets
                total_items: 2
                _embedded:
                  items:
                  - id: 1
                    name: Sale Items
                    rules: []
  "/shops/{shop_id}/posts":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Content
      summary: List shop posts
      description: Returns blog/news posts for the shop.
      operationId: listShopPosts
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/posts"
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
      responses:
        '200':
          description: Posts list
          content:
            application/json:
              example:
                _class:
                - results
                - posts
                total_items: 5
                _embedded:
                  items:
                  - id: 1
                    shop_id: 1
                    title: Summer Sale is Here
                    slug: summer-sale
                    published: true
                    published_on: '2024-06-01T00:00:00Z'
                    created_on: '2024-06-01T00:00:00Z'
    post:
      tags:
      - Content
      summary: Create a post
      operationId: createPost
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"title":"Winter Sale","body":"<p>Save big!</p>","published":true}' \
            "https://api.onbolder.com/v2/shops/1/posts"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - title
              - body
              properties:
                title:
                  type: string
                body:
                  type: string
                slug:
                  type: string
                  description: Auto-generated from title if blank
                description:
                  type: string
                short_description:
                  type: string
                published:
                  type: boolean
                  default: false
                published_on:
                  type: string
                  format: date-time
                format:
                  type: string
                  enum:
                  - html
                  - markdown
                  default: html
            example:
              title: Winter Sale
              body: "<p>Save big!</p>"
              published: true
      responses:
        '201':
          description: Post created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Post"
        '422':
          description: Validation error
  "/posts/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Post ID or slug
    get:
      tags:
      - Content
      summary: Get a post
      operationId: getPost
      responses:
        '200':
          description: Post entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Post"
        '404':
          description: Post not found
    put:
      tags:
      - Content
      summary: Update a post
      operationId: updatePost
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                body:
                  type: string
                published:
                  type: boolean
                published_on:
                  type: string
                  format: date-time
      responses:
        '200':
          description: Post updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Post"
    delete:
      tags:
      - Content
      summary: Delete a post
      operationId: deletePost
      responses:
        '204':
          description: Post deleted
  "/shops/{shop_id}/pages":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Content
      summary: List pages
      description: Returns static content pages for the shop.
      operationId: listShopPages
      responses:
        '200':
          description: Pages list
          content:
            application/json:
              example:
                _class:
                - results
                - pages
                total_items: 3
                _embedded:
                  items:
                  - id: 1
                    shop_id: 1
                    title: About Us
                    slug: about-us
                    visible: true
    post:
      tags:
      - Content
      summary: Create a page
      operationId: createPage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - title
              properties:
                title:
                  type: string
                body:
                  type: string
                slug:
                  type: string
                description:
                  type: string
                visible:
                  type: boolean
                  default: true
                format:
                  type: string
                  enum:
                  - html
                  - markdown
                  default: html
            example:
              title: About Us
              body: "<p>We are a small shop.</p>"
      responses:
        '201':
          description: Page created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Page"
        '422':
          description: Validation error
  "/pages/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Page ID or slug
    get:
      tags:
      - Content
      summary: Get a page
      operationId: getPage
      responses:
        '200':
          description: Page entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Page"
        '404':
          description: Page not found
    put:
      tags:
      - Content
      summary: Update a page
      operationId: updatePage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                slug:
                  type: string
                description:
                  type: string
                format:
                  type: string
                body:
                  type: string
                visible:
                  type: boolean
      responses:
        '200':
          description: Page updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Page"
    delete:
      tags:
      - Content
      summary: Delete a page
      operationId: deletePage
      responses:
        '204':
          description: Page deleted
  "/shops/{shop_id}/forms":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Content
      summary: List forms
      description: Returns contact/lead-capture forms for the shop.
      operationId: listShopForms
      responses:
        '200':
          description: Forms list
          content:
            application/json:
              example:
                _class:
                - results
                - forms
                total_items: 1
                _embedded:
                  items:
                  - id: 1
                    shop_id: 1
                    title: Contact Us
                    slug: contact-us
                    visible: true
    post:
      tags:
      - Content
      summary: Create a form
      operationId: createForm
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - title
              properties:
                title:
                  type: string
                body:
                  type: string
                  description: Intro text shown above the form fields
                slug:
                  type: string
                description:
                  type: string
                visible:
                  type: boolean
                  default: true
                format:
                  type: string
                  enum:
                  - html
                  - markdown
                  default: html
                has_address_fields:
                  type: boolean
                  default: false
                success_message:
                  type: string
            example:
              title: Contact Us
              has_address_fields: false
      responses:
        '201':
          description: Form created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Form"
        '422':
          description: Validation error
  "/forms/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Form ID or slug
    get:
      tags:
      - Content
      summary: Get a form
      operationId: getForm
      responses:
        '200':
          description: Form entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Form"
        '404':
          description: Form not found
    put:
      tags:
      - Content
      summary: Update a form
      operationId: updateForm
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                slug:
                  type: string
                description:
                  type: string
                format:
                  type: string
                body:
                  type: string
                visible:
                  type: boolean
                success_message:
                  type: string
                has_address_fields:
                  type: boolean
      responses:
        '200':
          description: Form updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Form"
    delete:
      tags:
      - Content
      summary: Delete a form
      operationId: deleteForm
      responses:
        '204':
          description: Form deleted
  "/shops/{shop_id}/shipping_tables":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: List shipping tables
      description: Returns shipping rate tables configured for the shop.
      operationId: listShippingTables
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/shipping_tables"
      responses:
        '200':
          description: Shipping tables list
          content:
            application/json:
              example:
                _class:
                - results
                - shippingTables
                total_items: 1
                _embedded:
                  items:
                  - id: 1
                    name: Standard Domestic
                    currency: USD
  "/shops/{shop_id}/shipping_tables/{id}":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Shops
      summary: Get a shipping table
      operationId: getShippingTable
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/shipping_tables/1"
      responses:
        '200':
          description: Shipping table with rates
          content:
            application/json:
              example:
                id: 1
                name: Standard Domestic
                currency: USD
                rows:
                - from_weight: 0
                  to_weight: 1000
                  price: 5.99
  "/contact_groups":
    get:
      tags:
      - Customers
      summary: List contact groups (flat)
      description: 'Returns customer groups (segments). Optionally filter by `shop_id`
        or `seller_id`; without either, returns groups across all of the account''s
        sellers.

        '
      operationId: listContactGroupsFlat
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
      - name: seller_id
        in: query
        schema:
          type: integer
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/contact_groups?seller_id=5"
      responses:
        '200':
          description: Contact groups list
          content:
            application/json:
              example:
                _class:
                - results
                - contactGroups
                total_items: 2
                _embedded:
                  items:
                  - id: 1
                    name: VIP Customers
                    customers_count: 42
    post:
      tags:
      - Customers
      summary: Create a contact group (flat)
      description: "`seller_id` is required in the body."
      operationId: createContactGroupFlat
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"seller_id":5,"name":"VIP Customers"}' \
            "https://api.onbolder.com/v2/contact_groups"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - seller_id
              properties:
                seller_id:
                  type: integer
                name:
                  type: string
                cart_requirements_list_id:
                  type: integer
                price_list_id:
                  type: integer
                allow_local_pickup:
                  type: boolean
                allow_shipping:
                  type: boolean
            example:
              seller_id: 5
              name: VIP Customers
      responses:
        '201':
          description: Contact group created
        '422':
          description: Validation error
  "/shops/{shop_id}/customer_groups":
    parameters:
    - name: shop_id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Customers
      summary: List contact groups (legacy shop-nested)
      description: Returns customer groups (segments) for the shop.
      operationId: listContactGroups
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/shops/1/customer_groups"
      responses:
        '200':
          description: Contact groups list
          content:
            application/json:
              example:
                _class:
                - results
                - contactGroups
                total_items: 2
                _embedded:
                  items:
                  - id: 1
                    name: VIP Customers
                    customers_count: 42
                    allow_local_pickup: true
                    allow_shipping: true
                    allow_signups: false
    post:
      tags:
      - Customers
      summary: Create a contact group (legacy shop-nested)
      description: Creates a customer group for the shop.
      operationId: createContactGroup
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"name":"VIP Customers"}' \
            "https://api.onbolder.com/v2/shops/1/customer_groups"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                cart_requirements_list_id:
                  type: integer
                price_list_id:
                  type: integer
                allow_local_pickup:
                  type: boolean
                allow_shipping:
                  type: boolean
            example:
              name: VIP Customers
      responses:
        '201':
          description: Contact group created
        '422':
          description: Validation error
  "/contact_groups/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Customers
      summary: Get a contact group
      operationId: getContactGroup
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/contact_groups/1"
      responses:
        '200':
          description: Contact group entity
          content:
            application/json:
              example:
                id: 1
                name: VIP Customers
                customers_count: 42
        '403':
          description: Access denied
        '404':
          description: Not found
    put:
      tags:
      - Customers
      summary: Update a contact group
      operationId: updateContactGroup
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X PUT \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"name":"Premium Customers"}' \
            "https://api.onbolder.com/v2/contact_groups/1"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                cart_requirements_list_id:
                  type: integer
                price_list_id:
                  type: integer
                allow_local_pickup:
                  type: boolean
                allow_shipping:
                  type: boolean
            example:
              name: Premium Customers
      responses:
        '200':
          description: Contact group updated
        '403':
          description: Access denied
    delete:
      tags:
      - Customers
      summary: Delete a contact group
      operationId: deleteContactGroup
      responses:
        '204':
          description: Contact group deleted
        '403':
          description: Access denied
  "/contact_groups/{id}/contacts":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
      example: 1
    get:
      tags:
      - Customers
      summary: List contacts in a group
      description: Returns all contacts belonging to this contact group.
      operationId: listContactGroupContacts
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/contact_groups/1/contacts"
      responses:
        '200':
          description: Contacts in this group
          content:
            application/json:
              example:
                _class:
                - results
                - customers
                total_items: 42
                _embedded:
                  items:
                  - id: 3001
                    email: jane@example.com
                    name: Jane Smith
  "/orders":
    get:
      tags:
      - Orders
      summary: List orders
      description: |
        Returns orders for a shop, or for a whole seller across all of its shops.

        `shop_id` and `seller_id` are both optional. If the token covers exactly
        one shop, that shop is auto-resolved and results are scoped to it. If the
        token covers multiple shops belonging to a single seller, omitting both
        params returns orders across all of that seller's shops. A token covering
        multiple sellers (or a god token) must pass `shop_id` or `seller_id`
        explicitly — there is no "all sellers" search.
      operationId: listOrders
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          # List all orders for a shop
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/orders?shop_id=1"

          # List orders across all of a seller's shops
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/orders?seller_id=1"

          # Filter by status
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/orders?shop_id=1&status=open"
      parameters:
      - name: shop_id
        in: query
        required: false
        schema:
          type: integer
        description: 'ID of the shop whose orders to list. Optional if the token covers
          exactly one shop, or if `seller_id` is given instead.

          '
        example: 1
      - name: seller_id
        in: query
        required: false
        schema:
          type: integer
        description: 'ID of the seller whose orders to list, across all of its shops.
          Only needed when `shop_id` is omitted and the token covers more than one
          shop or seller.

          '
        example: 1
      - name: status
        in: query
        schema:
          type: string
          enum:
          - draft
          - checkout
          - in_review
          - pending
          - cancelled
          - closed
          - completed
          - all
          - successful
          - abandoned
          default: all
        description: 'Filter by order status. Also accepts comma-separated values
          and `not_<status>` negations (e.g. `not_cancelled`), plus fulfillment statuses
          (`queued`, `preparing`, `ready`, `shipped`, `delivered`, `returned`, `unsent`).

          '
        example: pending
      - name: archived
        in: query
        schema:
          type: boolean
        description: Filter archived orders.
      - name: fulfillment_status
        in: query
        schema:
          type: string
          enum:
          - none
          - queued
          - preparing
          - ready
          - shipped
          - delivered
          - returned
      - name: contact_id
        in: query
        schema:
          type: integer
      - name: product_id
        in: query
        schema:
          type: integer
      - name: variant_id
        in: query
        schema:
          type: integer
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      - name: contact_email
        in: query
        schema:
          type: string
        description: Filter orders by customer email address.
        example: customer@example.com
      - name: code
        in: query
        schema:
          type: string
        description: Filter by exact order code (e.g. `ORD-00042`).
        example: ORD-00042
      - name: external_id
        in: query
        schema:
          type: string
        description: |
          Filter by exact client-supplied `external_id`. Useful for checking
          whether a previous create already succeeded before retrying it.
        example: ext-ord-9001
      - name: created_on_gte
        in: query
        schema:
          type: string
          format: date-time
        description: Orders placed on or after this ISO 8601 timestamp.
        example: '2024-01-01T00:00:00Z'
      - name: created_on_lte
        in: query
        schema:
          type: string
          format: date-time
        description: Orders placed on or before this ISO 8601 timestamp.
        example: '2024-12-31T23:59:59Z'
      - name: updated_on_gte
        in: query
        schema:
          type: string
          format: date-time
        description: Orders last updated on or after this ISO 8601 timestamp.
        example: '2024-06-01T00:00:00Z'
      - name: sort
        in: query
        schema:
          type: string
          enum:
          - created_on_asc
          - created_on_desc
          - updated_on_asc
          - updated_on_desc
          - total_asc
          - total_desc
          default: created_on_desc
        description: Sort order for results.
        example: created_on_desc
      responses:
        '200':
          description: Paginated list of orders
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderList"
              example:
                _class:
                - results
                - orders
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/orders?shop_id=1
                  shops:show:
                    href: https://api.onbolder.com/v2/shops/1
                _embedded:
                  orders:
                  - id: 5001
                    shop_id: 1
                    code: ORD-5001
                    status: pending
                    fulfillment_status: none
                    currency_code: USD
                    total: 8499
                    total_paid: 8499
                    created_on: '2024-06-15T09:45:00Z'
                    _embedded:
                      contact:
                        id: 42
                        name: Jane Doe
                        email: customer@example.com
                        phone_number: "+15551234567"
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/orders/ORD-5001
                      orders:update:
                        href: https://api.onbolder.com/v2/orders/ORD-5001
                        method: put
        '403':
          description: Access denied to the given shop or seller
        '422':
          description: "`shop_id` or `seller_id` required — the token covers more
            than one shop and more than one seller (or is a god token), so neither
            can be auto-resolved.\n"
    post:
      tags:
      - Orders
      summary: Create an order
      description: |
        Creates an order. **There is no `Idempotency-Key`-header-style replay
        mechanism** — a retried request always executes again server-side, it
        never returns a cached copy of the original response. If a create
        request times out or the response is otherwise lost, pass an
        `external_id` and check `GET /v2/orders?external_id={value}` before
        retrying: the first successful create claims that `external_id`, and a
        second create attempt with the same value is rejected with a 422
        instead of producing a duplicate order. (The `id`/`code` field does
        *not* give you this protection via `POST` — see below.)

        Alternatively, `PUT /v2/orders/{id}` with `_upsert: true` and an
        `external_id` in the body finds-or-creates by `external_id` directly
        — it creates the order (with a system-generated `code`, not the URL
        `:id`) if no order with that `external_id` exists yet, or updates it
        in place if one does, so a retried request converges instead of
        duplicating. See that operation's description for details.
      operationId: createOrder
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"shop_id":1,"contact":{"name":"Jane Doe","email":"customer@example.com"},"line_items":[{"variant_id":101,"units":2}]}' \
            "https://api.onbolder.com/v2/orders"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - shop_id
              properties:
                shop_id:
                  type: integer
                  description: Required — ID of the shop this order belongs to
                id:
                  type: string
                  description: |
                    Optional client-supplied order code (returned/queryable as
                    `code`). **Not a reliable idempotency key**: if the code
                    collides with an existing order, the collision is resolved
                    silently by generating a new random code instead of
                    rejecting the request — a retried create with the same `id`
                    does not return the original order, it creates a new one
                    with a different code. Use `external_id` instead if you need
                    safe dedup.
                external_id:
                  type: string
                  description: |
                    Optional client-supplied reference, unique per seller. A
                    colliding value is rejected with a 422 (unlike `id`), so this
                    is the field to use for retry-safe order creation: check
                    `GET /v2/orders?external_id={value}` before retrying a create
                    that may have already succeeded.
                source:
                  type: string
                  default: api
                contact:
                  type: object
                  properties:
                    id:
                      type: string
                      description: Existing contact ID, if known
                    name:
                      type: string
                    email:
                      type: string
                    phone_number:
                      type: string
                address:
                  "$ref": "#/components/schemas/Address"
                line_items:
                  type: array
                  items:
                    type: object
                    properties:
                      variant_id:
                        type: string
                      units:
                        type: integer
                        default: 1
                      price:
                        type: integer
                        description: Overrides the product's price, in cents
                      variant_sku:
                        type: string
                        description: Alternative to variant_id
                tags:
                  type: array
                  items:
                    type: string
                courier_name:
                  type: string
                tracking_code:
                  type: string
                fulfillment_method:
                  type: string
                  enum:
                  - shipping
                  - mailbox_pickup
                  - local_pickup
                  - none
                _policies:
                  type: object
                  description: Flags controlling order processing behavior
                  properties:
                    allow_no_stock:
                      type: boolean
                    allow_status:
                      type: boolean
                    force_item_prices:
                      type: boolean
                    allow_promotions:
                      type: boolean
                      default: true
            example:
              shop_id: 1
              contact:
                name: Jane Doe
                email: customer@example.com
              line_items:
              - variant_id: '101'
                units: 2
              - variant_id: '205'
                units: 1
      responses:
        '201':
          description: Order created
        '403':
          description: Access denied to the given shop
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
              example:
                id: 5002
                shop_id: 1
                code: ORD-5002
                status: checkout
                currency_code: USD
                total: 17997
                created_on: '2024-06-20T14:30:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/orders/ORD-5002
                  orders:update:
                    href: https://api.onbolder.com/v2/orders/ORD-5002
                    method: put
  "/orders/updaters":
    post:
      tags:
      - Orders
      summary: Batch update orders
      description: |
        Batch update multiple orders in a single request. `additions` accepts
        order fields (e.g. `status`, `fulfillment_status`, `courier_name`,
        `tracking_code`), plus `tags` to append.
      operationId: batchUpdateOrders
      parameters:
      - name: shop_id
        in: query
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - ids
              properties:
                ids:
                  type: array
                  items:
                    type: integer
                  description: Order IDs (or permalinks) to update
                additions:
                  type: object
                  description: 'Fields to set on every matching order (e.g. `status`,
                    `fulfillment_status`, `courier_name`, `tracking_code`), plus `tags`
                    to append.

                    '
                removals:
                  type: object
                  properties:
                    tags:
                      type: array
                      items:
                        type: string
                replacements:
                  type: object
                  properties:
                    tags:
                      type: array
                      items:
                        type: string
                decrement_stock:
                  type: boolean
                  default: true
                notify_status_change:
                  type: boolean
                  default: false
                notification_message:
                  type: string
            example:
              ids:
              - 5001
              - 5002
              additions:
                status: closed
                tags:
                - batch-updated
      responses:
        '201':
          description: Orders processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  ids:
                    type: array
                    items:
                      type: integer
                  additions:
                    type: object
                  removals:
                    type: object
                  _embedded:
                    type: object
                    properties:
                      valid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            code:
                              type: string
                            status:
                              type: string
                      invalid_items:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            code:
                              type: string
                            status:
                              type: string
              example:
                ids:
                - 5001
                - 5002
                additions:
                  status: closed
                _embedded:
                  valid_items:
                  - id: 5001
                    code: ORD-5001
                    status: closed
                  invalid_items: []
        '422':
          description: Validation error (e.g. missing `ids`, or neither `additions`/`removals`/`replacements`
            given)
  "/orders/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Order ID or permalink
    get:
      tags:
      - Orders
      summary: Get an order
      description: |
        Direct access to an order by ID or permalink. No `shop_id` needed in the URL —
        authorization is verified via the token's account ownership.
      operationId: getOrder
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/orders/ORD-5001"
      responses:
        '200':
          description: Order entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
              example:
                id: 5001
                shop_id: 1
                code: ORD-5001
                status: pending
                fulfillment_status: none
                currency_code: USD
                total: 8499
                total_paid: 8499
                created_on: '2024-06-15T09:45:00Z'
                updated_on: '2024-06-15T09:45:00Z'
                _embedded:
                  contact:
                    id: 42
                    name: Jane Doe
                    email: customer@example.com
                    phone_number: "+15551234567"
                  line_items:
                  - id: 9001
                    product_id: 101
                    product_name: T-Shirt
                    variant_id: 201
                    variant_name: Medium / Blue
                    units: 2
                    unit_price: 2500
                    net_total: 5000
                    total: 5000
                _links:
                  self:
                    href: https://api.onbolder.com/v2/orders/ORD-5001
                  orders:update:
                    href: https://api.onbolder.com/v2/orders/ORD-5001
                    method: put
                  shops:show:
                    href: https://api.onbolder.com/v2/shops/1
        '403':
          description: Order belongs to a different account
        '404':
          description: Order not found
    put:
      tags:
      - Orders
      summary: Update an order
      description: |
        Setting `status` to most values requires `_policies.allow_status: true`
        in the request body.

        ## Recommended pattern for retry-safe order creation

        `POST /v2/orders` has no idempotency mechanism (see its description).
        The recommended way to create an order safely under retry is
        **`PUT /v2/orders/{id}` with `_upsert: true` and an `external_id`**
        in the body:

        - If no order exists yet with that `external_id` (scoped to the
          seller resolved via `shop_id`), one is created (response status
          `201`) using the same request body fields as `POST /v2/orders`
          (`shop_id`, `contact`, `line_items`, etc). The order gets a normal
          system-generated `code` — the URL `:id` is **not** used as, or
          turned into, the order's code; a client cannot pick the public
          order code this way. `:id` can be any placeholder (e.g. the same
          `external_id` value, for readability), since matching is done by
          `external_id`, not by the URL.
        - If an order already exists with that `external_id`, it's updated
          in place (response status `200`) — a retried request converges on
          the same order instead of creating a duplicate.
        - Without `_upsert: true`, `PUT` to a nonexistent `:id` returns `404`
          as usual.
        - `_upsert: true` without an `external_id` in the body returns `422`
          — the flag requires `external_id` so a typo in the URL `:id` alone
          can't silently create an unintended order.

        This is the same `external_id` field documented on `Order` and on
        `POST /v2/orders` — a collision there is rejected with `422`;
        here, under `_upsert`, a "collision" is the intended match and
        updates the existing order instead.

        ## Stock restoration on cancellation

        Setting `status` to `cancelled` automatically restores/releases reserved
        stock, but only when both of these hold:

        - the order's `fulfillment_status` is still unsent (`queued`,
          `preparing`, or `ready` — not yet `shipped`/`delivered`), and
        - stock was actually reserved for the order in the first place (line
          items on non-stock-tracked products, or without a `variant_id` at all,
          never reserved anything, so there's nothing to restore for them).

        Cancelling an order that has already shipped does **not** auto-restore
        stock. Restoration is independent of payment state — it isn't affected by
        whether the order was paid or refunded.
      operationId: updateOrder
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X PUT \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"tracking_code":"1Z9999W99999999999","courier_name":"UPS"}' \
            "https://api.onbolder.com/v2/orders/ORD-5001"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                _upsert:
                  type: boolean
                  default: false
                  description: |
                    If `true`, requires `external_id` in this same request
                    body. Finds an order by `external_id` (scoped to the
                    seller); if none exists, creates one instead of returning
                    404 (with a system-generated `code`, not the URL `:id`).
                    See "Recommended pattern for retry-safe order creation"
                    above.
                status:
                  type: string
                  enum:
                  - draft
                  - checkout
                  - in_review
                  - pending
                  - cancelled
                  - closed
                  - completed
                fulfillment_status:
                  type: string
                  enum:
                  - none
                  - queued
                  - preparing
                  - ready
                  - shipped
                  - delivered
                  - returned
                archived:
                  type: boolean
                external_id:
                  type: string
                  description: |
                    Optional client-supplied reference, unique per seller. Can be
                    set here if it wasn't given at create time; a colliding value
                    is rejected with a 422.
                tracking_code:
                  type: string
                courier_name:
                  type: string
                fulfillment_method:
                  type: string
                  enum:
                  - shipping
                  - mailbox_pickup
                  - local_pickup
                  - none
                shipping_amount:
                  type: integer
                shipping_discount:
                  type: integer
                shipping_description:
                  type: string
                tags:
                  type: array
                  items:
                    type: string
                notify_status_change:
                  type: boolean
                  default: false
                notification_message:
                  type: string
                _policies:
                  type: object
                  properties:
                    allow_status:
                      type: boolean
                      description: Required to change `status` to most values
                    append_tags:
                      type: boolean
            example:
              tracking_code: 1Z9999W99999999999
              courier_name: UPS
              fulfillment_status: shipped
      responses:
        '200':
          description: Order updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
              example:
                id: 5001
                code: ORD-5001
                status: pending
                fulfillment_status: shipped
                tracking_code: 1Z9999W99999999999
                courier_name: UPS
                _links:
                  self:
                    href: https://api.onbolder.com/v2/orders/ORD-5001
  "/orders/{id}/refund":
    post:
      tags:
      - Orders
      summary: Refund an order
      operationId: refundOrder
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  description: Refund amount in cents
                description:
                  type: string
                reason:
                  type: string
                  enum:
                  - duplicate
                  - fraudulent
                  - customer_request
                  - other
                authorization_code:
                  type: string
                  description: Reference code for manual refunds
                manual:
                  type: boolean
                  default: false
                  description: Skip the payment gateway and record the refund directly
            example:
              amount: 2500
              reason: customer_request
      responses:
        '200':
          description: Refund processed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderTransaction"
        '422':
          description: Order is not refundable, or amount exceeds max_refundable_amount
  "/orders/{id}/split":
    post:
      tags:
      - Orders
      summary: Split an order
      operationId: splitOrder
      description: |
        Splits a subset of line items (in full or partial quantity) off the original order
        into a new, separate order. The new order carries only the specified items and can
        be transitioned through the fulfillment lifecycle independently of the remainder.

        Typical use case: a partial return where some items are sent back while others are
        kept, or fulfilling items from different warehouses on separate timelines.
      parameters:
      - name: id
        in: path
        required: true
        description: Order code or numeric ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - items
              properties:
                items:
                  type: array
                  description: Line items to move into the new order
                  items:
                    type: object
                    required:
                    - order_item_id
                    - quantity
                    properties:
                      order_item_id:
                        type: integer
                        description: ID of the line item to split
                      quantity:
                        type: integer
                        description: Number of units to move (must be ≤ the item's
                          current quantity)
            example:
              items:
              - order_item_id: 12345
                quantity: 1
              - order_item_id: 12346
                quantity: 2
      responses:
        '201':
          description: New order created from the split items
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
        '422':
          description: Invalid split (e.g. quantity exceeds available units, no items
            given, or item not found on the order)
  "/orders/{order_id}/documents":
    get:
      tags:
      - Orders
      summary: List order documents
      operationId: listOrderDocuments
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Documents list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          "$ref": "#/components/schemas/OrderDocument"
    post:
      tags:
      - Orders
      summary: Create an order document
      operationId: createOrderDocument
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - document_type
              - uploader_id
              - uploader_type
              properties:
                document_type:
                  type: string
                  enum:
                  - invoice
                  - receipt
                document_number:
                  type: string
                file_url:
                  type: string
                transaction_id:
                  type: string
                  description: Associates the document with a specific payment/refund
                    transaction
                uploader_id:
                  type: string
                uploader_type:
                  type: string
            example:
              document_type: invoice
              document_number: INV-0001
              uploader_id: '1'
              uploader_type: User
      responses:
        '201':
          description: Document created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderDocument"
  "/orders/{order_id}/documents/{id}":
    get:
      tags:
      - Orders
      summary: Get an order document
      operationId: getOrderDocument
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Document
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderDocument"
    delete:
      tags:
      - Orders
      summary: Delete an order document
      operationId: deleteOrderDocument
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '204':
          description: Document deleted
  "/orders/{order_id}/items":
    parameters:
    - name: order_id
      in: path
      required: true
      schema:
        type: string
    post:
      tags:
      - Orders
      summary: Add a line item to an order
      description: |
        Adds a single item to the order. Accepts the same fields as one element
        of the `line_items` array on order create/update. If the variant is
        already in the order, units are updated instead (upsert by variant).
      operationId: createOrderItem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                variant_id:
                  type: string
                variant_sku:
                  type: string
                  description: Alternative to variant_id, for linked items
                units:
                  type: integer
                  default: 1
                price:
                  type: integer
                  description: Overrides the product's price, in cents
                product_name:
                  type: string
                  description: Required for detached items (no product record)
            examples:
              linked_item:
                summary: Linked item (existing product)
                value:
                  variant_id: '123'
                  units: 2
              detached_item:
                summary: Detached item (no product record)
                value:
                  product_name: Custom Engraving
                  variant_sku: ENG-01
                  price: 1500
                  units: 1
      responses:
        '200':
          description: Updated order (with the new line item)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
  "/orders/{order_id}/items/{id}":
    parameters:
    - name: order_id
      in: path
      required: true
      schema:
        type: string
    - name: id
      in: path
      required: true
      schema:
        type: integer
      description: Order item (line item) ID
    put:
      tags:
      - Orders
      summary: Update an order line item
      description: Updates units and/or price on an existing line item. Order totals
        are recalculated.
      operationId: updateOrderItem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                units:
                  type: integer
                price:
                  type: integer
                  description: Force a new price, in cents
            example:
              units: 3
      responses:
        '200':
          description: Updated order
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
    delete:
      tags:
      - Orders
      summary: Remove a line item from an order
      description: 'Returns the updated order (200), not 204, so the caller sees the
        new totals immediately.

        '
      operationId: deleteOrderItem
      responses:
        '200':
          description: Updated order
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Order"
  "/orders/{order_id}/payments":
    parameters:
    - name: order_id
      in: path
      required: true
      schema:
        type: string
    post:
      tags:
      - Orders
      summary: Record a manual payment on an order
      description: 'Records a manual payment (cash, bank transfer, external processor,
        etc.) against an order. Does **not** go through any payment gateway — the
        payment is recorded as-is and the order''s paid amount is updated immediately.

        '
      operationId: createOrderPayment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - amount
              properties:
                amount:
                  type: integer
                  description: Payment amount in cents
                authorization_code:
                  type: string
                  description: Reference / receipt number
                description:
                  type: string
                transaction_id:
                  type: string
                  description: External transaction identifier
            example:
              amount: 5000
              description: Wire transfer ref. XYZ
      responses:
        '201':
          description: Payment recorded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OrderTransaction"
  "/orders/{order_id}/checkout_session":
    parameters:
    - name: order_id
      in: path
      required: true
      schema:
        type: string
    post:
      tags:
      - Orders
      summary: Start (or resume) checkout for an order
      description: 'Starts, or returns the existing active, checkout session for an
        order. Idempotent while a session is still active — calling this repeatedly
        before expiry returns the same token.

        '
      operationId: createCheckoutSession
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                return_url:
                  type: string
                  description: 'Where to redirect the buyer after a hosted-redirect-mode
                    gateway round-trip. Its host must match the shop''s own domain
                    or one of its configured allowed embed origins — any other host
                    is rejected with 422, to prevent open redirects. Only applied
                    if the session doesn''t already have a `return_url` (first caller
                    wins); ignored on later idempotent calls.

                    '
            example:
              return_url: https://mystore.com/order/confirmation
      responses:
        '200':
          description: Checkout session
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CheckoutSession"
        '422':
          description: 'Either this shop is not on the session-based checkout, or
            `return_url` was given but its host isn''t allowed for this shop

            '
  "/checkout_sessions/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: The checkout session token (not a numeric ID)
    get:
      tags:
      - Orders
      summary: Get a checkout session
      description: 'Resolves a checkout session by its token. Does not auto-rotate
        an expired session the way the hosted checkout page does — an expired session
        returns 410 with a `replacement` session in the body.

        '
      operationId: getCheckoutSession
      responses:
        '200':
          description: Checkout session
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CheckoutSession"
        '410':
          description: Session expired
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  replacement:
                    "$ref": "#/components/schemas/CheckoutSession"
  "/checkout_sessions/{id}/view":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    post:
      tags:
      - Orders
      summary: Mark a checkout session as viewed
      description: 'Records `last_viewed_at`/`view_count`. Not a "checkout complete"
        signal — order status/payment state is never driven by this.

        '
      operationId: viewCheckoutSession
      responses:
        '200':
          description: Checkout session
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/CheckoutSession"
  "/customers":
    get:
      tags:
      - Customers
      summary: List customers
      description: |
        Returns customers for a shop, or for a whole seller across all of its shops.

        `shop_id` and `seller_id` are both optional. If the token covers exactly
        one shop, that shop is auto-resolved and results are scoped to it. If the
        token covers multiple shops belonging to a single seller, omitting both
        params returns customers across all of that seller's shops. A token
        covering multiple sellers (or a god token) must pass `shop_id` or
        `seller_id` explicitly — there is no "all sellers" search.
      operationId: listCustomers
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          # List all customers for a shop
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/customers?shop_id=1"

          # List customers across all of a seller's shops
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/customers?seller_id=1"

          # Search by name or email
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/customers?shop_id=1&q=jane"
      parameters:
      - name: shop_id
        in: query
        required: false
        schema:
          type: integer
        description: 'ID of the shop whose contacts to list. Optional if the token
          covers exactly one shop, or if `seller_id` is given instead.

          '
        example: 1
      - name: seller_id
        in: query
        required: false
        schema:
          type: integer
        description: 'ID of the seller whose contacts to list, across all of its shops.
          Only needed when `shop_id` is omitted and the token covers more than one
          shop or seller.

          '
        example: 1
      - name: q
        in: query
        schema:
          type: string
        description: Full-text search across name and email.
        example: jane
      - name: customer_status
        in: query
        schema:
          type: string
          enum:
          - public
          - private
          - pending
        description: Filter by customer account status.
        example: private
      - name: tags
        in: query
        schema:
          type: string
        description: Comma-separated tag names.
      - name: opt_in
        in: query
        schema:
          type: boolean
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      - name: created_on_gte
        in: query
        schema:
          type: string
          format: date-time
        description: Contacts created on or after this ISO 8601 timestamp.
        example: '2024-01-01T00:00:00Z'
      - name: created_on_lte
        in: query
        schema:
          type: string
          format: date-time
        description: Contacts created on or before this ISO 8601 timestamp.
        example: '2024-12-31T23:59:59Z'
      - name: sort
        in: query
        schema:
          type: string
          enum:
          - created_on_asc
          - created_on_desc
          - name_asc
          - name_desc
          default: created_on_desc
        description: Sort order for results.
        example: created_on_desc
      responses:
        '200':
          description: Paginated list of contacts
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ContactList"
              example:
                _class:
                - results
                - customers
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers?shop_id=1
                  customers:authenticate:
                    href: https://api.onbolder.com/v2/customers/sessions
                    method: post
                  customers:reset_password:
                    href: https://api.onbolder.com/v2/customers/reset_password
                    method: post
                _embedded:
                  items:
                  - id: 3001
                    shop_id: 1
                    email: jane@example.com
                    name: Jane Smith
                    customer_status: private
                    phone_validated: false
                    created_on: '2024-03-10T11:00:00Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/customers/3001
                      orders:
                        href: https://api.onbolder.com/v2/orders?shop_id=1&contact_email=jane@example.com
        '403':
          description: Access denied
        '422':
          description: "`shop_id` or `seller_id` required — the token covers more
            than one shop and more than one seller (or is a god token), so neither
            can be auto-resolved.\n"
    post:
      tags:
      - Customers
      summary: Create a customer
      description: 'If a contact with the given `email` already exists for this shop,
        it is updated rather than duplicated.

        '
      operationId: createCustomer
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X POST \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"shop_id":1,"email":"newcustomer@example.com","name":"Jane Smith"}' \
            "https://api.onbolder.com/v2/customers"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - "$ref": "#/components/schemas/ContactInput"
              - type: object
                required:
                - shop_id
                properties:
                  shop_id:
                    type: integer
            example:
              shop_id: 1
              email: newcustomer@example.com
              name: Jane Smith
      responses:
        '201':
          description: Customer created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Contact"
              example:
                id: 3001
                shop_id: 1
                email: newcustomer@example.com
                name: Jane Smith
                customer_status: public
                phone_validated: false
                created_on: '2024-06-20T14:00:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers/3001
                  orders:
                    href: https://api.onbolder.com/v2/orders?shop_id=1&contact_email=newcustomer@example.com
        '422':
          description: Validation error
  "/customers/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Customers
      summary: Get a customer
      operationId: getCustomer
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/customers/3001"
      responses:
        '200':
          description: Customer entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Contact"
              example:
                id: 3001
                shop_id: 1
                email: jane@example.com
                name: Jane Smith
                customer_status: private
                phone_number: "+15551234567"
                phone_validated: true
                created_on: '2024-03-10T11:00:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers/3001
                  orders:
                    href: https://api.onbolder.com/v2/orders?shop_id=1&contact_email=jane@example.com
                  customers:update:
                    href: https://api.onbolder.com/v2/customers/3001
                    method: put
        '403':
          description: Access denied
        '404':
          description: Customer not found
    put:
      tags:
      - Customers
      summary: Update a customer
      operationId: updateCustomer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ContactInput"
            example:
              name: Janet Smith-Jones
              phone_number: "+15559876543"
      responses:
        '200':
          description: Customer updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Contact"
              example:
                id: 3001
                email: jane@example.com
                name: Janet Smith-Jones
                phone_number: "+15559876543"
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers/3001
  "/customers/{id}/last_ordered_products":
    get:
      tags:
      - Customers
      summary: Get last ordered products
      operationId: getCustomerLastOrderedProducts
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Last ordered products
  "/customers/{id}/activations":
    post:
      tags:
      - Customers
      summary: Start contact activation
      operationId: startContactActivation
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '201':
          description: Activation started
  "/customers/{id}/phone_validations":
    post:
      tags:
      - Customers
      summary: Start phone validation
      operationId: startPhoneValidation
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '201':
          description: Phone validation started
  "/customers/{id}/activation":
    delete:
      tags:
      - Customers
      summary: Demote a contact
      operationId: demoteContact
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '204':
          description: Contact demoted
  "/customers/sessions":
    post:
      tags:
      - Customers
      summary: Authenticate a customer
      description: |
        Validates email + password credentials for a shop's customer account.
        Returns the contact entity with a session token on success.
        Requires a Bearer token with `customers.write` scope (the same token used for
        all v2 requests) and `shop_id` in the request body.
      operationId: authenticateCustomer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - password
              - shop_id
              properties:
                shop_id:
                  type: integer
                  description: ID of the shop the customer belongs to.
                email:
                  type: string
                password:
                  type: string
            example:
              shop_id: 1
              email: jane@example.com
              password: s3cr3tpassword
      responses:
        '200':
          description: Authentication successful
          content:
            application/json:
              example:
                id: 3001
                email: jane@example.com
                name: Jane Smith
                customer_status: private
                token: eyJhbGciOiJIUzI1NiJ9...
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers/3001
        '422':
          description: Invalid credentials
  "/customers/reset_password":
    post:
      tags:
      - Customers
      summary: Request a password reset
      description: |
        Sends a password-reset email to the customer's address.
        Returns 200 even if the email is not found, to avoid user enumeration.
        Use the 422 response only for missing `shop_id`.
      operationId: resetCustomerPassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - shop_id
              properties:
                shop_id:
                  type: integer
                email:
                  type: string
            example:
              shop_id: 1
              email: jane@example.com
      responses:
        '200':
          description: Reset email sent (or silently ignored if not found)
          content:
            application/json:
              example:
                message: If that email is registered, a reset link has been sent.
        '422':
          description: "`shop_id` required"
  "/customers/activations/{token}":
    put:
      tags:
      - Customers
      summary: Activate a customer account
      description: |
        Completes the customer account activation flow by setting the initial
        password. The `token` is sent to the customer's email after the
        merchant triggers `customers:start_activation`.
      operationId: activateCustomer
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
        description: Activation token from the email link.
        example: abc123def456ghi789
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - password
              properties:
                password:
                  type: string
                  description: The new password for the account (min 8 chars).
            example:
              password: newSecureP@ss1
      responses:
        '200':
          description: Account activated
          content:
            application/json:
              example:
                id: 3001
                email: jane@example.com
                status: private
                _links:
                  self:
                    href: https://api.onbolder.com/v2/customers/3001
        '422':
          description: Invalid or expired token
  "/price_lists":
    get:
      tags:
      - Price Lists
      summary: List price lists
      description: |
        Returns price lists for the authenticated account. A price list lets
        you override variant prices for specific customer groups or channels.
      operationId: listPriceLists
      parameters:
      - name: seller_id
        in: query
        schema:
          type: integer
        description: Filter price lists by seller ID.
        example: 7
      - name: currency
        in: query
        schema:
          type: string
        description: Filter price lists by currency code (ISO 4217).
        example: EUR
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      responses:
        '200':
          description: Paginated list of price lists
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PriceListList"
              example:
                _class:
                - results
                - price_lists
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/price_lists
                  products:create_price_list:
                    href: https://api.onbolder.com/v2/price_lists
                    method: post
                _embedded:
                  items:
                  - id: 10
                    name: Wholesale EUR
                    currency_code: EUR
                    created_on: '2024-01-15T10:00:00Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/price_lists/10
    post:
      tags:
      - Price Lists
      summary: Create a price list
      operationId: createPriceList
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - seller_id
              properties:
                seller_id:
                  type: integer
                  description: The seller this price list belongs to.
                name:
                  type: string
                  description: Human-readable label for this price list.
                prices_include_tax:
                  type: boolean
                  description: Whether prices in this list already include tax.
                use_price_comparison_for_vol_discounts:
                  type: boolean
                  description: Use price comparison values when computing volume discounts.
                contact_group_ids:
                  type: array
                  items:
                    type: integer
                  description: 'Contact groups this price list applies to. On update,
                    this list is additive (existing group ids are not removed).

                    '
                tax:
                  type: object
                  properties:
                    name:
                      type: string
                    rate:
                      type: number
                      description: Tax rate, between 0 and 1.
              description: 'Note: `currency_code` is not a settable field — it is
                always derived from the owning seller''s currency and returned read-only
                in responses.

                '
            example:
              seller_id: 7
              name: Wholesale EUR
              prices_include_tax: false
      responses:
        '201':
          description: Price list created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PriceList"
              example:
                id: 10
                name: Wholesale EUR
                currency_code: EUR
                created_on: '2024-01-15T10:00:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/price_lists/10
                  products:add_price_item:
                    href: https://api.onbolder.com/v2/price_lists/10/items
                    method: post
  "/price_lists/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Price Lists
      summary: Get a price list
      operationId: getPriceList
      responses:
        '200':
          description: Price list entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PriceList"
              example:
                id: 10
                name: Wholesale EUR
                currency_code: EUR
                contact_groups_count: 3
                created_on: '2024-01-15T10:00:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/price_lists/10
                  products:price_items:
                    href: https://api.onbolder.com/v2/price_lists/10/items
                  products:add_price_item:
                    href: https://api.onbolder.com/v2/price_lists/10/items
                    method: post
                  products:delete_price_list:
                    href: https://api.onbolder.com/v2/price_lists/10
                    method: delete
        '403':
          description: Access denied
    put:
      tags:
      - Price Lists
      summary: Update a price list
      operationId: updatePriceList
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                prices_include_tax:
                  type: boolean
                use_price_comparison_for_vol_discounts:
                  type: boolean
                contact_group_ids:
                  type: array
                  items:
                    type: integer
                  description: Additive — existing group ids on the price list are
                    not removed.
                tax:
                  type: object
                  properties:
                    name:
                      type: string
                    rate:
                      type: number
            example:
              name: Wholesale EUR - Revised
      responses:
        '200':
          description: Price list updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PriceList"
              example:
                id: 10
                name: Wholesale EUR - Revised
                currency_code: EUR
                _links:
                  self:
                    href: https://api.onbolder.com/v2/price_lists/10
    delete:
      tags:
      - Price Lists
      summary: Delete a price list
      operationId: deletePriceList
      responses:
        '204':
          description: Price list deleted
  "/price_lists/{id}/products":
    get:
      tags:
      - Price Lists
      summary: List products in a price list
      description: Returns all products that have a price override within this price
        list.
      operationId: getPriceListProducts
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Price list ID
        example: 10
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
      responses:
        '200':
          description: Products in this price list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          type: object
                          description: 'There is no `list_prices` field — price overrides
                            are embedded as `price_items` (per variant).

                            '
                          properties:
                            id:
                              type: integer
                            product_id:
                              type: integer
                            name:
                              type: string
                            slug:
                              type: string
                            price:
                              type: integer
                            price_comparison:
                              type: integer
                            retail_price:
                              type: integer
                              description: Original (non-price-list) price, in cents
              example:
                _class:
                - results
                - priceListProducts
                total_items: 3
                _embedded:
                  items:
                  - id: 101
                    product_id: 101
                    name: Summer Dress
                    slug: summer-dress
                    price: 3999
                    retail_price: 4999
                    _embedded:
                      price_items:
                      - id: 201
                        variant_id: 201
                        price: 3999
        '404':
          description: Price list not found
  "/price_lists/{price_list_id}/items":
    get:
      tags:
      - Price Lists
      summary: List price items
      description: Returns all price item overrides within the given price list.
      operationId: listPriceItems
      parameters:
      - name: price_list_id
        in: path
        required: true
        schema:
          type: integer
        example: 10
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
      responses:
        '200':
          description: Price items list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          "$ref": "#/components/schemas/PriceItem"
              example:
                _class:
                - results
                - priceItems
                total_items: 2
                _embedded:
                  items:
                  - id: 201
                    variant_id: 201
                    product_id: 101
                    name: Summer Dress
                    sku: SD-001
                    price: 3999
                    retail_price: 4999
                    visible: true
  "/price_lists/{price_list_id}/items/{id}":
    parameters:
    - name: price_list_id
      in: path
      required: true
      schema:
        type: integer
      example: 10
    - name: id
      in: path
      required: true
      schema:
        type: integer
      description: 'The variant ID. Passing a variant ID that has no price override
        in this price list returns 404.

        '
      example: 201
    put:
      tags:
      - Price Lists
      summary: Update a price item
      description: Set or update the price override for a variant in this price list.
      operationId: updatePriceItem
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                price:
                  type: integer
                  description: In cents
            example:
              price: 3499
      responses:
        '200':
          description: Price item updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PriceItem"
    delete:
      tags:
      - Price Lists
      summary: Delete a price item
      description: Remove the price override for this variant from the price list.
      operationId: deletePriceItem
      responses:
        '204':
          description: Price item deleted
  "/promotions":
    get:
      tags:
      - Promotions
      summary: List promotions
      description: |
        Returns discount promotions. Promotions apply percentage or
        fixed-amount discounts via coupon codes or automatic rules.
      operationId: listPromotions
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: ID of the shop whose promotions to list.
        example: 1
      - name: status
        in: query
        schema:
          type: string
          enum:
          - active
          - expired
          - all
          default: all
        description: Filter by promotion status.
        example: active
      - name: q
        in: query
        schema:
          type: string
        description: Search by promotion name or code.
        example: SUMMER20
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      responses:
        '200':
          description: Paginated promotions list
          content:
            application/json:
              example:
                _class:
                - results
                - promotions
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/promotions?shop_id=1
                _embedded:
                  items:
                  - id: 20
                    name: Summer Sale 20%
                    code: SUMMER20
                    activation_mode: code
                    discount_type: percentage
                    discount: 20
                    status: active
                    starts_at: '2024-06-01T00:00:00Z'
                    expires_at: '2024-08-31T23:59:59Z'
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/promotions/20
        '422':
          description: "`shop_id` required"
        '403':
          description: Access denied
    post:
      tags:
      - Promotions
      summary: Create a promotion
      description: |
        Creates a new promotion for a shop. Promotions apply percentage or fixed-amount
        discounts via coupon codes or automatic rules.
        `shop_id` is required in the body.
      operationId: createPromotion
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PromotionInput"
            example:
              shop_id: 1
              name: Summer Sale
              code: SUMMER20
              discount_type: percentage
              discount: 20
              activation_mode: code
              starts_at: '2024-06-01T00:00:00Z'
              expires_at: '2024-08-31T23:59:59Z'
      responses:
        '201':
          description: Promotion created
          content:
            application/json:
              example:
                id: 20
                name: Summer Sale
                code: SUMMER20
                discount_type: percentage
                discount: 20
                status: active
                starts_at: '2024-06-01T00:00:00Z'
                expires_at: '2024-08-31T23:59:59Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/promotions/20
        '422':
          description: Validation error
  "/promotions/{id}":
    get:
      tags:
      - Promotions
      summary: Get a promotion
      description: Returns a single promotion by ID.
      operationId: getPromotion
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the promotion to retrieve.
        example: 20
      responses:
        '200':
          description: Promotion details
          content:
            application/json:
              example:
                id: 20
                name: Summer Sale
                code: SUMMER20
                discount_type: percentage
                discount: 20
                status: active
                starts_at: '2024-06-01T00:00:00Z'
                expires_at: '2024-08-31T23:59:59Z'
                usage_count: 0
                _links:
                  self:
                    href: https://api.onbolder.com/v2/promotions/20
        '404':
          description: Promotion not found
    put:
      tags:
      - Promotions
      summary: Update a promotion
      description: Updates an existing promotion.
      operationId: updatePromotion
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the promotion to update.
        example: 20
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PromotionInput"
            example:
              name: Summer Sale Extended
              discount: 25
      responses:
        '200':
          description: Promotion updated
          content:
            application/json:
              example:
                id: 20
                name: Summer Sale Extended
                discount: 25
                _links:
                  self:
                    href: https://api.onbolder.com/v2/promotions/20
        '404':
          description: Promotion not found
    delete:
      tags:
      - Promotions
      summary: Delete a promotion
      description: Soft-deletes a promotion.
      operationId: deletePromotion
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the promotion to delete.
        example: 20
      responses:
        '204':
          description: Promotion deleted
  "/volume_discounts":
    get:
      tags:
      - Volume Discounts
      summary: List volume discounts
      description: |
        Returns volume discount rules. Volume discounts automatically
        apply tiered pricing when a customer orders above a quantity threshold.
      operationId: listVolumeDiscounts
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
        description: ID of the shop whose volume discounts to list.
        example: 1
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
        example: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 200
        example: 20
      responses:
        '200':
          description: Paginated volume discounts list
          content:
            application/json:
              example:
                _class:
                - results
                - volume_discounts
                total_items: 1
                per_page: 20
                page: 1
                _links:
                  self:
                    href: https://api.onbolder.com/v2/volume_discounts?shop_id=1
                _embedded:
                  items:
                  - id: 30
                    name: Buy 5+ get 10% off
                    discount_type: percentage
                    tiers:
                      '5': 10
                      '10': 15
                    _links:
                      self:
                        href: https://api.onbolder.com/v2/volume_discounts/30
        '422':
          description: "`shop_id` required"
        '403':
          description: Access denied
    post:
      tags:
      - Volume Discounts
      summary: Create a volume discount
      description: |
        Creates a new volume discount for a shop. Volume discounts automatically
        apply tiered pricing when a customer orders above a unit threshold.
        `shop_id` is required in the body.
      operationId: createVolumeDiscount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/VolumeDiscountInput"
            example:
              shop_id: 1
              name: Buy 5+ get 10% off
              discount_type: percentage
              tiers:
              - from: 5
                value: 10
              - from: 10
                value: 15
              - from: 20
                value: 20
      responses:
        '201':
          description: Volume discount created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/VolumeDiscount"
              example:
                id: 30
                name: Buy 5+ get 10% off
                discount_type: percentage
                tiers:
                  '5': 10
                  '10': 15
                  '20': 20
                _links:
                  self:
                    href: https://api.onbolder.com/v2/volume_discounts/30
        '422':
          description: Validation error
  "/volume_discounts/{id}":
    get:
      tags:
      - Volume Discounts
      summary: Get a volume discount
      description: Returns a single volume discount by ID.
      operationId: getVolumeDiscount
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the volume discount to retrieve.
        example: 30
      responses:
        '200':
          description: Volume discount details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/VolumeDiscount"
              example:
                id: 30
                name: Buy 5+ get 10% off
                discount_type: percentage
                tiers:
                  '5': 10
                cumulative: false
                _links:
                  self:
                    href: https://api.onbolder.com/v2/volume_discounts/30
        '404':
          description: Volume discount not found
    put:
      tags:
      - Volume Discounts
      summary: Update a volume discount
      description: Updates an existing volume discount.
      operationId: updateVolumeDiscount
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the volume discount to update.
        example: 30
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/VolumeDiscountInput"
            example:
              tiers:
              - from: 5
                value: 15
      responses:
        '200':
          description: Volume discount updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/VolumeDiscount"
              example:
                id: 30
                tiers:
                  '5': 15
                _links:
                  self:
                    href: https://api.onbolder.com/v2/volume_discounts/30
        '404':
          description: Volume discount not found
    delete:
      tags:
      - Volume Discounts
      summary: Delete a volume discount
      description: Soft-deletes a volume discount.
      operationId: deleteVolumeDiscount
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: ID of the volume discount to delete.
        example: 30
      responses:
        '204':
          description: Volume discount deleted
  "/themes/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Theme ID
      example: abc123
    get:
      tags:
      - Themes
      summary: Get a theme
      description: |
        Returns the full theme entity including all embedded templates and assets.
        The `production` field indicates whether this is the live theme.
        For the public/production theme, `store:create_dev_theme` is available.
        For a dev theme, `store:publish_theme` and `store:delete_theme` are available.
      operationId: getTheme
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
            "https://api.onbolder.com/v2/themes/abc123"
      responses:
        '200':
          description: Theme entity with embedded templates and assets
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Theme"
              example:
                id: abc123
                title: My Theme
                production: true
                public: true
                accessible: true
                linked: false
                parent_id:
                settings: '{"bg_color":"#fff"}'
                created_on: '2023-06-01T10:00:00Z'
                updated_on: '2024-11-15T09:30:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/themes/abc123
                  store:update_theme:
                    href: https://api.onbolder.com/v2/themes/abc123
                    method: put
                  store:create_template:
                    href: https://api.onbolder.com/v2/themes/abc123/templates
                    method: post
                  store:create_theme_asset:
                    href: https://api.onbolder.com/v2/themes/abc123/assets
                    method: post
                  store:theme_preview:
                    href: https://myshop.onbolder.com
                    type: text/html
                  store:create_dev_theme:
                    href: https://api.onbolder.com/v2/shops/1/themes
                    method: post
                _embedded:
                  templates:
                  - id: t1
                    file_name: layout.html
                    name: layout
                    content_type: text/html
                    body: "<html>...</html>"
                    is_section: false
                    updated_on: '2024-11-15T09:30:00Z'
                  assets:
                  - id: a1
                    file_name: logo.png
                    content_type: image/png
                    file_size: 12400
                    url: https://cdn.onbolder.com/themes/abc123/logo.png
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Theme not found
    put:
      tags:
      - Themes
      summary: Update a theme
      description: Update theme settings or title.
      operationId: updateTheme
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -s -X PUT \
            -H "Authorization: Bearer $BOLDER_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"title":"My Updated Theme"}' \
            "https://api.onbolder.com/v2/themes/abc123"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                settings_json:
                  type: string
                  description: 'Raw JSON string of theme settings. Note: the request
                    field is `settings_json`, not `settings` (the response field,
                    returned as `settings`, is named differently from the request
                    field).

                    '
                parent_id:
                  type: string
                  description: Reassigns the dev theme's parent (production) theme.
                last_updated_on:
                  type: string
                  format: date-time
                  description: Optimistic-locking timestamp; rejects the update if
                    the theme changed since.
            example:
              title: Updated Theme Title
              settings_json: '{"bg_color":"#333"}'
      responses:
        '200':
          description: Theme updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Theme"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Theme not found
    delete:
      tags:
      - Themes
      summary: Delete a dev theme
      description: |
        Deletes the dev theme. The production theme cannot be deleted.
        The `store:delete_theme` link is only present on dev themes.
      operationId: deleteTheme
      responses:
        '204':
          description: Theme deleted
        '403':
          description: Cannot delete the production theme
        '404':
          description: Theme not found
  "/themes/{id}/publication":
    put:
      tags:
      - Themes
      summary: Publish a dev theme
      description: |
        Promotes the dev theme to production. The current production theme is
        archived and the dev theme becomes the new live theme.
        Only available when `store:publish_theme` is present in the dev theme's links.
      operationId: publishTheme
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Dev theme ID
        example: def456
      responses:
        '200':
          description: Theme published — returns the now-production theme
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Theme"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Theme not found
  "/themes/{theme_id}/templates":
    post:
      tags:
      - Themes
      summary: Create a template
      description: |
        Creates a new template file inside the theme. The `file_name` determines
        the template type (e.g. `layout.html`, `product.html`, `custom.css`).
      operationId: createTemplate
      parameters:
      - name: theme_id
        in: path
        required: true
        schema:
          type: string
        description: Theme ID
        example: abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - file_name
              properties:
                file_name:
                  type: string
                  description: Template filename (determines type)
                  example: collection.html
                body:
                  type: string
                  description: Template source code
                  example: "<h1>{{ collection.name }}</h1>"
            example:
              file_name: collection.html
              body: "<h1>{{ collection.name }}</h1>"
      responses:
        '201':
          description: Template created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ThemeTemplate"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '422':
          description: Validation error
  "/themes/{theme_id}/templates/settings":
    put:
      tags:
      - Themes
      summary: Update theme settings
      description: Replaces the theme's global settings.
      operationId: updateThemeSettings
      parameters:
      - name: theme_id
        in: path
        required: true
        schema:
          type: string
        description: Theme ID
        example: abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - settings_json
              properties:
                settings_json:
                  type: string
                  description: JSON string containing the theme settings blob
                  example: '{"color":"#ff0000","font":"Helvetica"}'
            example:
              settings_json: '{"color":"#ff0000","font":"Helvetica"}'
      responses:
        '200':
          description: Settings updated; returns the settings template stub
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ThemeTemplate"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '422':
          description: Validation error
  "/themes/{theme_id}/templates/{id}":
    parameters:
    - name: theme_id
      in: path
      required: true
      schema:
        type: string
      description: Theme ID
      example: abc123
    - name: id
      in: path
      required: true
      schema:
        type: string
      description: Template ID or filename slug
      example: layout.html
    get:
      tags:
      - Themes
      summary: Get a template
      description: Returns a single template including its full body source.
      operationId: getTemplate
      responses:
        '200':
          description: Template entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ThemeTemplate"
              example:
                id: t1
                file_name: layout.html
                name: layout
                content_type: text/html
                body: "<html><head></head><body>{{ content }}</body></html>"
                is_section: false
                updated_on: '2024-11-15T09:30:00Z'
                _links:
                  self:
                    href: https://api.onbolder.com/v2/themes/abc123/templates/t1
                  store:update_template:
                    href: https://api.onbolder.com/v2/themes/abc123/templates/t1
                    method: put
                  store:delete_template:
                    href: https://api.onbolder.com/v2/themes/abc123/templates/t1
                    method: delete
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Template not found
    put:
      tags:
      - Themes
      summary: Update a template
      description: Replace the body of an existing template.
      operationId: updateTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                body:
                  type: string
                  description: New template source code
            example:
              body: "<html><head><title>{{ shop.name }}</title></head><body>{{ content
                }}</body></html>"
      responses:
        '200':
          description: Template updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ThemeTemplate"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Template not found
    delete:
      tags:
      - Themes
      summary: Delete a template
      description: |
        Deletes a non-required template from the theme. Required templates
        (e.g. `layout.html`) cannot be deleted — the `store:delete_template`
        link is absent for those.
      operationId: deleteTemplate
      responses:
        '204':
          description: Template deleted
        '403':
          description: Cannot delete a required template
        '404':
          description: Template not found
  "/themes/{theme_id}/assets":
    post:
      tags:
      - Themes
      summary: Upload a theme asset
      description: |
        Uploads a new file asset (image, font, stylesheet, etc.) to the theme.
        Send the file as a multipart/form-data upload.
      operationId: createThemeAsset
      parameters:
      - name: theme_id
        in: path
        required: true
        schema:
          type: string
        description: Theme ID
        example: abc123
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - file
              properties:
                file:
                  type: string
                  format: binary
                  description: File to upload
      responses:
        '201':
          description: Asset uploaded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ThemeAsset"
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '422':
          description: Validation error
  "/themes/{theme_id}/assets/{id}":
    delete:
      tags:
      - Themes
      summary: Delete a theme asset
      description: |
        Permanently deletes an asset from the theme. The `store:delete_theme_asset`
        link is present on each asset entity.
      operationId: deleteThemeAsset
      parameters:
      - name: theme_id
        in: path
        required: true
        schema:
          type: string
        description: Theme ID
        example: abc123
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Asset ID
        example: a1
      responses:
        '204':
          description: Asset deleted
        '401':
          description: Unauthorized
        '403':
          description: Access denied
        '404':
          description: Asset not found
  "/subscription_purchases":
    get:
      tags:
      - Subscriptions
      summary: List subscription purchases
      description: Returns customer subscriptions.
      operationId: listSubscriptionPurchases
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
      - name: seller_ids
        in: query
        schema:
          type: string
        description: Comma-separated seller IDs.
      - name: state
        in: query
        schema:
          type: string
      - name: contact_id
        in: query
        schema:
          type: integer
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: Subscription purchases list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          "$ref": "#/components/schemas/SubscriptionPurchase"
        '422':
          description: "`shop_id` or `seller_ids` is required"
  "/subscription_purchases/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Subscriptions
      summary: Get a subscription purchase
      operationId: getSubscriptionPurchase
      responses:
        '200':
          description: Subscription purchase entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionPurchase"
        '404':
          description: Not found
    put:
      tags:
      - Subscriptions
      summary: Update a subscription purchase
      description: Only the fields listed below are writable.
      operationId: updateSubscriptionPurchase
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                state:
                  type: string
                next_payment_on:
                  type: string
                  format: date
                total_renewal_fee:
                  type: integer
                remaining_payments:
                  type: integer
                subscription_fee_id:
                  type: integer
                cancel_at:
                  type: string
                  format: date
                payments_to_skip:
                  type: integer
                paused_by:
                  type: string
                last_pause_at:
                  type: string
                  format: date-time
                renewal_discount:
                  type: number
                discount_num_charges:
                  type: integer
                end_date:
                  type: string
                  format: date
            example:
              state: paused
              paused_by: admin
      responses:
        '200':
          description: Subscription purchase updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionPurchase"
        '422':
          description: Validation error (e.g. invalid `state`, or no updatable fields
            provided)
  "/subscription_purchases/{id}/renew":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    put:
      tags:
      - Subscriptions
      summary: Record a manual renewal
      description: 'Advances `last_payment_on`/`next_payment_on` and sets `state`
        back to `active`. Decrements `remaining_payments` if the subscription has
        a fixed number of payments left.

        '
      operationId: renewSubscriptionPurchase
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                payment_date:
                  type: string
                  format: date
                  description: Defaults to today if omitted
      responses:
        '200':
          description: Subscription purchase renewed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionPurchase"
        '422':
          description: Invalid `payment_date`, or validation error
  "/subscription_fees":
    get:
      tags:
      - Subscriptions
      summary: List subscription fees
      operationId: listSubscriptionFees
      parameters:
      - name: shop_id
        in: query
        schema:
          type: integer
      - name: product_id
        in: query
        schema:
          type: integer
      - name: variant_id
        in: query
        schema:
          type: integer
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: per_page
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: Subscription fees list
          content:
            application/json:
              schema:
                type: object
                properties:
                  _embedded:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          "$ref": "#/components/schemas/SubscriptionFee"
    post:
      tags:
      - Subscriptions
      summary: Create a subscription fee
      description: Attaches recurring-payment terms to a product (and optionally a
        specific variant).
      operationId: createSubscriptionFee
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - product_id
              - payment_interval
              properties:
                product_id:
                  type: integer
                variant_id:
                  type: integer
                payment_interval:
                  type: string
                  enum:
                  - 1_week
                  - 1_month
                  - 3_months
                  - 6_months
                  - 12_months
                renewal_fee:
                  type: integer
                  description: In cents
                setup_fee:
                  type: integer
                  description: In cents
                number_of_payments:
                  type: integer
                payment_day:
                  type: integer
                by_percentage:
                  type: boolean
                allow_non_recurring_payment:
                  type: boolean
                min_days_for_cancellation:
                  type: integer
                allow_changing_day:
                  type: boolean
                extend_on_reenable:
                  type: boolean
                on_first_renewal:
                  type: string
            example:
              product_id: 101
              payment_interval: 1_month
              renewal_fee: 2999
      responses:
        '201':
          description: Subscription fee created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionFee"
        '422':
          description: Validation error
  "/subscription_fees/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: integer
    get:
      tags:
      - Subscriptions
      summary: Get a subscription fee
      operationId: getSubscriptionFee
      responses:
        '200':
          description: Subscription fee entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionFee"
        '404':
          description: Not found
    put:
      tags:
      - Subscriptions
      summary: Update a subscription fee
      operationId: updateSubscriptionFee
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                payment_interval:
                  type: string
                  enum:
                  - 1_week
                  - 1_month
                  - 3_months
                  - 6_months
                  - 12_months
                renewal_fee:
                  type: integer
                setup_fee:
                  type: integer
                number_of_payments:
                  type: integer
                payment_day:
                  type: integer
                update_purchases_fees:
                  type: boolean
                  description: Also apply the new fee terms to existing active purchases
                notify_purchases:
                  type: boolean
      responses:
        '200':
          description: Subscription fee updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/SubscriptionFee"
        '422':
          description: Validation error
    delete:
      tags:
      - Subscriptions
      summary: Delete a subscription fee
      operationId: deleteSubscriptionFee
      responses:
        '204':
          description: Subscription fee deleted
        '422':
          description: Cannot delete (e.g. in use by active purchases)
