{
  "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, ...).\n\nThis API is the public Shopify storefront surface of the shop:\n- **Catalog (no auth)** - product and collection JSON, search suggestions, recommendations.\n- **Cart (no auth, cookie session)** - Shopify AJAX Cart API. Errors are JSON `{status, message, description}`.\n- **Agent commerce** - Universal Commerce Protocol (UCP) over MCP JSON-RPC 2.0 at `/api/ucp/mcp`. Payment always requires explicit buyer approval.\n\nPrices 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": [],
        "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": [],
        "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": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of products to return (1-250).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 30
            }
          },
          {
            "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": [],
        "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": [],
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "description": "Collection URL handle.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            },
            "example": "best-sellers"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of products to return (1-250).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 30
            }
          },
          {
            "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"
                }
              }
            }
          }
        }
      }
    },
    "/search/suggest.json": {
      "get": {
        "operationId": "searchProducts",
        "tags": [
          "Catalog"
        ],
        "summary": "Search products by keyword",
        "description": "Predictive search. Returns matching products with price and URL.",
        "security": [],
        "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": {
                  "$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": [],
        "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": {
                  "$ref": "#/components/schemas/AjaxError"
                },
                "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": [],
        "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": [],
        "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": {
                  "$ref": "#/components/schemas/AjaxError"
                },
                "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": [],
        "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": {
                  "$ref": "#/components/schemas/AjaxError"
                },
                "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": {}
            }
          }
        }
      }
    }
  }
}
