openapi: 3.1.0
info:
  title: Vintagebulk Storefront & Agent Commerce API
  version: '2026-10-09'
  summary: Read the Vintagebulk wholesale vintage clothing catalog, manage a cart and check out via UCP/MCP.
  description: 'Vintagebulk (https://vintagebulk.nl) is a Dutch B2B wholesale supplier of graded, second-hand branded clothing
    sold in boxes and bales to resellers (Ralph Lauren, Tommy Hilfiger, Carhartt, Nike, The North Face, ...).


    This API is the public Shopify storefront surface of the shop:

    - **Catalog (no auth)** - product and collection JSON, search suggestions, recommendations.

    - **Cart (no auth, cookie session)** - Shopify AJAX Cart API. Errors are JSON `{status, message, description}`.

    - **Agent commerce** - Universal Commerce Protocol (UCP) over MCP JSON-RPC 2.0 at `/api/ucp/mcp`. Payment always requires
    explicit buyer approval.


    Prices in catalog JSON are EUR. Agent instructions: https://vintagebulk.nl/agents.md . Sandbox: no test mode exists on
    the live shop; use the read-only endpoints freely, or https://mock.shop (free mock Shopify Storefront API) to build and
    test integrations without placing orders.'
  contact:
    name: Vintagebulk
    url: https://vintagebulk.nl/pages/developers
    email: info@vintagebulk.nl
  termsOfService: https://vintagebulk.nl/policies/terms-of-service
externalDocs:
  description: Vintagebulk developer documentation
  url: https://vintagebulk.nl/pages/developers
servers:
- url: https://vintagebulk.nl
  description: Production storefront (EUR)
security: []
tags:
- name: Discovery
  description: Machine-readable discovery documents.
- name: Catalog
  description: Read-only product data. No authentication.
- name: Cart
  description: Shopify AJAX Cart API. Session is the `cart` cookie; no API key.
- name: Agent commerce (UCP/MCP)
  description: UCP tools over MCP JSON-RPC 2.0.
paths:
  /.well-known/ucp:
    get:
      operationId: getUcpProfile
      tags:
      - Discovery
      summary: Get the UCP merchant profile
      description: Returns supported UCP versions, the MCP service endpoint, capabilities and payment handlers. Call this
        first before transacting.
      security: &id001 []
      responses:
        '200':
          description: UCP merchant profile.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /agents.md:
    get:
      operationId: getAgentInstructions
      tags:
      - Discovery
      summary: Get agent instructions (Markdown)
      description: Canonical agent-facing description of the store, including when to use Vintagebulk. Mirrored at /llms.txt.
      security: *id001
      responses:
        '200':
          description: Markdown document.
          content:
            text/markdown:
              schema:
                type: string
  /products.json:
    get:
      operationId: listProducts
      tags:
      - Catalog
      summary: List all published products
      description: Paginated list of every published product with variants, prices (EUR, decimal strings) and images.
      security: *id001
      parameters:
      - &id002
        name: limit
        in: query
        required: false
        description: Maximum number of products to return (1-250).
        schema:
          type: integer
          minimum: 1
          maximum: 250
          default: 30
      - &id003
        name: page
        in: query
        required: false
        description: 1-based page number for pagination.
        schema:
          type: integer
          minimum: 1
          default: 1
      responses:
        '200':
          description: Product list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'
  /products/{handle}.json:
    get:
      operationId: getProductByHandle
      tags:
      - Catalog
      summary: Get one product by handle
      description: Full product record including all variants and their prices and availability.
      security: *id001
      parameters:
      - name: handle
        in: path
        required: true
        description: Product URL handle.
        schema:
          type: string
          pattern: ^[a-z0-9-]+$
        example: adidas-schoenen-box
      responses:
        '200':
          description: Product.
          content:
            application/json:
              schema:
                type: object
                required:
                - product
                properties:
                  product:
                    $ref: '#/components/schemas/Product'
        '404':
          description: No published product with this handle (empty JSON body).
  /collections/{handle}/products.json:
    get:
      operationId: listCollectionProducts
      tags:
      - Catalog
      summary: List products in a collection
      description: Products in one collection, e.g. `best-sellers`, `boxes`, `budget-tot-70`, or `all`. Unknown handles return
        an empty list.
      security: *id001
      parameters:
      - name: handle
        in: path
        required: true
        description: Collection URL handle.
        schema:
          type: string
          pattern: ^[a-z0-9-]+$
        example: best-sellers
      - *id002
      - *id003
      responses:
        '200':
          description: Product list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'
  /search/suggest.json:
    get:
      operationId: searchProducts
      tags:
      - Catalog
      summary: Search products by keyword
      description: Predictive search. Returns matching products with price and URL.
      security: *id001
      parameters:
      - name: q
        in: query
        required: true
        description: Search terms, e.g. `ralph lauren polo`.
        schema:
          type: string
          minLength: 1
      - name: resources[type]
        in: query
        required: false
        description: Comma-separated resource types.
        schema:
          type: string
          default: product
          enum:
          - product
          - collection
          - page
          - article
          - query
      - name: resources[limit]
        in: query
        required: false
        description: Max results per type (1-10).
        schema:
          type: integer
          minimum: 1
          maximum: 10
          default: 10
      responses:
        '200':
          description: Search results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  resources:
                    type: object
                    additionalProperties: true
        '422':
          description: Missing or empty `q`.
          content:
            application/json:
              schema: &id004
                $ref: '#/components/schemas/AjaxError'
              example:
                status: 422
                message: Invalid parameter error
                description: 'param is missing or the value is empty: q'
  /recommendations/products.json:
    get:
      operationId: getProductRecommendations
      tags:
      - Catalog
      summary: Get related products
      description: Products related or complementary to a given product id.
      security: *id001
      parameters:
      - name: product_id
        in: query
        required: true
        description: Numeric product id from listProducts.
        schema:
          type: integer
      - name: limit
        in: query
        required: false
        description: Max results (1-10).
        schema:
          type: integer
          minimum: 1
          maximum: 10
          default: 4
      - name: intent
        in: query
        required: false
        description: Recommendation intent.
        schema:
          type: string
          enum:
          - related
          - complementary
          default: related
      responses:
        '200':
          description: Recommended products.
          content:
            application/json:
              schema:
                type: object
                properties:
                  intent:
                    type: string
                  products:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '404':
          description: Unknown product id.
          content:
            application/json:
              schema: *id004
              example:
                status: 404
                message: Product not found
                description: No product with id 1 is published in the online store
  /cart.js:
    get:
      operationId: getCart
      tags:
      - Cart
      summary: Get the current cart
      description: Cart for the session identified by the `cart` cookie. Prices are integers in euro cents.
      security: *id001
      responses:
        '200':
          description: Cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
  /cart/add.js:
    post:
      operationId: addToCart
      tags:
      - Cart
      summary: Add variants to the cart
      description: Adds one or more variants (by numeric variant id) to the session cart.
      security: *id001
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - items
              properties:
                items:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    required:
                    - id
                    - quantity
                    properties:
                      id:
                        type: integer
                        description: Variant id.
                      quantity:
                        type: integer
                        minimum: 1
            example:
              items:
              - id: 56657175183746
                quantity: 1
      responses:
        '200':
          description: Added line items.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '422':
          description: Variant not found, sold out or quantity unavailable.
          content:
            application/json:
              schema: *id004
              example:
                status: 422
                message: Cart Error
                description: Cannot find variant
  /cart/change.js:
    post:
      operationId: changeCartLine
      tags:
      - Cart
      summary: Change the quantity of a cart line
      description: Sets the quantity of an existing line (0 removes it).
      security: *id001
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - id
              - quantity
              properties:
                id:
                  type: string
                  description: Line item key or variant id.
                quantity:
                  type: integer
                  minimum: 0
            example:
              id: '56657175183746'
              quantity: 2
      responses:
        '200':
          description: Updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '422':
          description: Line not in cart.
          content:
            application/json:
              schema: *id004
              example:
                status: 422
                message: Cart Error
                description: Cannot find variant
  /api/ucp/mcp:
    post:
      operationId: callUcpMcp
      tags:
      - Agent commerce (UCP/MCP)
      summary: Call a UCP commerce tool over MCP (JSON-RPC 2.0)
      description: 'MCP Streamable-HTTP endpoint. Methods: `initialize`, `tools/list`, `tools/call`. Tools: search_catalog,
        lookup_catalog, get_product, create_cart, get_cart, update_cart, cancel_cart, create_checkout, get_checkout, update_checkout,
        complete_checkout, cancel_checkout, get_order. Catalog and cart tools work without a token; buyer-linked checkout/order
        access uses the OAuth scopes below. `complete_checkout` must only be called after explicit buyer approval.'
      security:
      - {}
      - shopifyCustomerAccount:
        - dev.ucp.shopping.catalog.search:read
      - shopifyCustomerAccount:
        - dev.ucp.shopping.checkout:manage
      - shopifyCustomerAccount:
        - customer-account-mcp-api:full
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JsonRpcRequest'
            examples:
              listTools:
                value:
                  jsonrpc: '2.0'
                  id: 1
                  method: tools/list
              searchCatalog:
                value:
                  jsonrpc: '2.0'
                  id: 2
                  method: tools/call
                  params:
                    name: search_catalog
                    arguments:
                      catalog:
                        query: ralph lauren polo box
      responses:
        '200':
          description: JSON-RPC result or JSON-RPC error object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonRpcResponse'
        '400':
          description: Malformed JSON-RPC request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonRpcResponse'
              example:
                jsonrpc: '2.0'
                id: 0
                error:
                  code: -32600
                  message: Invalid Request
                  data: Invalid JSON in request body.
        '429':
          description: Rate limited per IP; retry with exponential backoff.
components:
  securitySchemes:
    shopifyCustomerAccount:
      type: oauth2
      description: 'Shopify Customer Account OAuth 2.0 (authorization code + PKCE S256). Metadata: https://vintagebulk.nl/.well-known/oauth-authorization-server
        and RFC 9728 resource metadata at https://vintagebulk.nl/.well-known/oauth-protected-resource. Request only the scopes
        you need.'
      flows:
        authorizationCode:
          authorizationUrl: https://account.vintagebulk.nl/authentication/oauth/authorize
          tokenUrl: https://account.vintagebulk.nl/authentication/oauth/token
          refreshUrl: https://account.vintagebulk.nl/authentication/oauth/token
          scopes:
            openid: Authenticate the buyer (OpenID Connect).
            email: Read the buyer's email address.
            dev.ucp.shopping.catalog.search:read: Search the catalog on behalf of the buyer (read-only).
            dev.ucp.shopping.checkout:manage: Create, update and complete checkouts for the buyer (payment still needs buyer
              approval).
            customer-account-mcp-api:full: Use the buyer's account (orders, addresses) via MCP.
            customer-account-api:full: Full Customer Account API access for the buyer.
    shopifyOpenIdConnect:
      type: openIdConnect
      openIdConnectUrl: https://vintagebulk.nl/.well-known/openid-configuration
      description: OpenID Connect discovery for the same Shopify Customer Account authorization server.
  schemas:
    AjaxError:
      type: object
      description: Structured error returned by the storefront AJAX/JSON endpoints.
      required:
      - status
      - message
      - description
      properties:
        status:
          type: integer
          description: HTTP status code.
          example: 422
        message:
          type: string
          description: Short error category.
          example: Cart Error
        description:
          type: string
          description: Human-readable cause and hint for resolution.
          example: Cannot find variant
    Variant:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        price:
          type: string
          description: Decimal EUR price, e.g. "89.95".
        compare_at_price:
          type:
          - string
          - 'null'
        available:
          type: boolean
        sku:
          type:
          - string
          - 'null'
    Product:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        handle:
          type: string
        body_html:
          type:
          - string
          - 'null'
        vendor:
          type: string
        product_type:
          type: string
        tags:
          oneOf:
          - type: string
          - type: array
            items:
              type: string
        variants:
          type: array
          items:
            $ref: '#/components/schemas/Variant'
        images:
          type: array
          items:
            type: object
            properties:
              src:
                type: string
                format: uri
    ProductList:
      type: object
      required:
      - products
      properties:
        products:
          type: array
          items:
            $ref: '#/components/schemas/Product'
    Cart:
      type: object
      properties:
        token:
          type: string
        item_count:
          type: integer
        total_price:
          type: integer
          description: Euro cents.
        currency:
          type: string
          example: EUR
        items:
          type: array
          items:
            type: object
            additionalProperties: true
    JsonRpcRequest:
      type: object
      required:
      - jsonrpc
      - method
      - id
      properties:
        jsonrpc:
          type: string
          const: '2.0'
        id:
          type:
          - integer
          - string
        method:
          type: string
          enum:
          - initialize
          - tools/list
          - tools/call
        params:
          type: object
          additionalProperties: true
    JsonRpcResponse:
      type: object
      required:
      - jsonrpc
      properties:
        jsonrpc:
          type: string
          const: '2.0'
        id:
          type:
          - integer
          - string
          - 'null'
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: integer
            message:
              type: string
            data: {}
