{ "openapi": "3.1.0", "info": { "title": "Ozzi Storefront API", "summary": "Public, unauthenticated read and cart API for the Ozzi storefront.", "description": "Ozzi sells a natural GLP-1 support drink stick and capsule. This specification describes the public storefront surface an agent can call without any credential: catalog reads, product lookup, search, and cart mutation. Transactional checkout is not described here — it is handled by the Universal Commerce Protocol MCP server at https://heyozzi.com/api/ucp/mcp, discoverable at https://heyozzi.com/.well-known/ucp and described at https://heyozzi.com/.well-known/mcp.json. Human-readable docs: https://heyozzi.com/developers. Agent instructions: https://heyozzi.com/agents.md.\n\nNo credential is required and none is issued: there is no API key to request, no OAuth handshake, no sandbox to provision and no sales call. Production is the only environment, and it is safe to explore — nothing is charged until a human approves payment.\n\nTo verify the JSON error contract before you rely on it, call either of these. Both are idempotent, take no arguments and change nothing:\n\n GET https://heyozzi.com/search/suggest.json\n GET https://heyozzi.com/recommendations/products.json\n\nEach returns HTTP 422 with a JSON body shaped {\"status\", \"message\", \"description\"} — see the Error schema.", "version": "1.0.0", "termsOfService": "https://heyozzi.com/policies/terms-of-service", "contact": { "name": "Ozzi Support", "email": "help@heyozzi.com", "url": "https://heyozzi.com/pages/contact" }, "license": { "name": "Proprietary", "identifier": "LicenseRef-Ozzi-Proprietary" } }, "servers": [ { "url": "https://heyozzi.com", "description": "Production storefront" } ], "externalDocs": { "description": "Ozzi developer documentation", "url": "https://heyozzi.com/developers" }, "tags": [ { "name": "catalog", "description": "Read products, variants, prices and availability. No credential required." }, { "name": "search", "description": "Find products by free-text query." }, { "name": "cart", "description": "Read and mutate the session cart. Session-scoped, no credential required." }, { "name": "discovery", "description": "Machine-readable descriptions of this store for agents." } ], "security": [ { "publicRead": [ "catalog:read" ] } ], "paths": { "/products/{handle}.js": { "get": { "operationId": "getProductByHandle", "summary": "Get a product by handle", "description": "Returns a single product with all variants, prices in minor units (cents), availability, images and options. This is the endpoint to read live pricing from; do not cache prices from documentation.", "tags": [ "catalog" ], "security": [ { "publicRead": [ "catalog:read" ] } ], "parameters": [ { "name": "handle", "in": "path", "required": true, "description": "The product's URL handle. Known handles: crave-crusher, crave-crusher-pills, ozzi-sample-packs.", "schema": { "type": "string", "pattern": "^[a-z0-9-]+$", "examples": [ "crave-crusher" ] } } ], "responses": { "200": { "description": "The product.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } } }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/collections/{handle}/products.json": { "get": { "operationId": "listCollectionProducts", "summary": "List products in a collection", "description": "Returns a paginated list of products in the named collection. Use the handle 'all' for the full catalog.", "tags": [ "catalog" ], "security": [ { "publicRead": [ "catalog:read" ] } ], "parameters": [ { "name": "handle", "in": "path", "required": true, "description": "Collection handle. Use 'all' for every published product.", "schema": { "type": "string", "pattern": "^[a-z0-9-]+$", "default": "all", "examples": [ "all" ] } }, { "name": "limit", "in": "query", "required": false, "description": "Number of products per page.", "schema": { "type": "integer", "minimum": 1, "maximum": 250, "default": 30 } }, { "name": "page", "in": "query", "required": false, "description": "1-based page number.", "schema": { "type": "integer", "minimum": 1, "default": 1 } } ], "responses": { "200": { "description": "A page of products.", "content": { "application/json": { "schema": { "type": "object", "required": [ "products" ], "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } } }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/search/suggest.json": { "get": { "operationId": "searchProducts", "summary": "Search products by free text", "description": "Returns products matching a free-text query. Use this to resolve a shopper's phrasing to a product handle before calling getProductByHandle.", "tags": [ "search" ], "security": [ { "publicRead": [ "catalog:read" ] } ], "parameters": [ { "name": "q", "in": "query", "required": true, "description": "The search query.", "schema": { "type": "string", "minLength": 1, "examples": [ "crave crusher" ] } }, { "name": "resources[type]", "in": "query", "required": false, "description": "Comma-separated resource types to search.", "schema": { "type": "string", "default": "product", "examples": [ "product" ] } }, { "name": "resources[limit]", "in": "query", "required": false, "description": "Maximum results per resource type.", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5 } } ], "responses": { "200": { "description": "Search results.", "content": { "application/json": { "schema": { "type": "object", "properties": { "resources": { "type": "object", "properties": { "results": { "type": "object", "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } } } } } } }, "422": { "$ref": "#/components/responses/UnprocessableEntity" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/recommendations/products.json": { "get": { "operationId": "getProductRecommendations", "summary": "Get products related to a given product", "description": "Returns products Shopify considers complementary to the supplied product id. Useful for building a bundle or suggesting an add-on. Omitting product_id returns a structured JSON 422, which makes this the cheapest way to verify the error contract without changing any state.", "tags": [ "catalog" ], "security": [ { "publicRead": [ "catalog:read" ] } ], "parameters": [ { "name": "product_id", "in": "query", "required": true, "description": "The product id to find recommendations for, from Product.id.", "schema": { "type": "integer", "format": "int64" } }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of recommendations to return.", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 4 } } ], "responses": { "200": { "description": "Recommended products.", "content": { "application/json": { "schema": { "type": "object", "required": [ "products" ], "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } } }, "422": { "$ref": "#/components/responses/UnprocessableEntity" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/cart.js": { "get": { "operationId": "getCart", "summary": "Get the current cart", "description": "Returns the session cart with line items and totals in minor units (cents). The cart is identified by the session cookie, not by a credential.", "tags": [ "cart" ], "security": [ { "sessionCart": [ "cart:write" ] } ], "responses": { "200": { "description": "The current cart.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cart" } } } }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/cart/add.js": { "post": { "operationId": "addCartItems", "summary": "Add line items to the cart", "description": "Adds one or more variants to the session cart. Returns HTTP 422 with a structured JSON error when a variant does not exist, is sold out, or breaches a quantity limit. Do not retry a 422 unchanged.", "tags": [ "cart" ], "security": [ { "sessionCart": [ "cart:write" ] } ], "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", "format": "int64", "description": "The variant id, from Product.variants[].id." }, "quantity": { "type": "integer", "minimum": 1, "default": 1 }, "properties": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Custom line-item properties." }, "selling_plan": { "type": "integer", "format": "int64", "description": "Subscription selling plan id, for a recurring purchase." } } } } } } } } }, "responses": { "200": { "description": "The added line items.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cart" } } } }, "422": { "$ref": "#/components/responses/UnprocessableEntity" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/cart/change.js": { "post": { "operationId": "changeCartItem", "summary": "Change the quantity of a cart line", "description": "Updates the quantity of an existing line item. Set quantity to 0 to remove the line.", "tags": [ "cart" ], "security": [ { "sessionCart": [ "cart:write" ] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "id", "quantity" ], "properties": { "id": { "type": "string", "description": "The line item key, from Cart.items[].key." }, "quantity": { "type": "integer", "minimum": 0 } } } } } }, "responses": { "200": { "description": "The updated cart.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cart" } } } }, "422": { "$ref": "#/components/responses/UnprocessableEntity" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/.well-known/ucp": { "get": { "operationId": "getUcpProfile", "summary": "Get the Universal Commerce Protocol merchant profile", "description": "Returns supported UCP versions, service endpoints, capabilities and payment handlers. Start here to discover the MCP endpoint for transactional commerce.", "tags": [ "discovery" ], "security": [ {} ], "responses": { "200": { "description": "The UCP merchant profile.", "content": { "application/json": { "schema": { "type": "object", "required": [ "ucp" ], "properties": { "ucp": { "type": "object", "properties": { "version": { "type": "string" }, "supported_versions": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } }, "services": { "type": "object" }, "capabilities": { "type": "object" }, "payment_handlers": { "type": "object" } } } } } } } } } } }, "/api/ucp/mcp": { "post": { "operationId": "callMcpEndpoint", "summary": "Call the Ozzi MCP server (Universal Commerce Protocol)", "description": "JSON-RPC 2.0 over Streamable HTTP. Call 'initialize' to negotiate, then 'tools/list' to discover the catalog, cart and checkout tools with their JSON Schemas, then 'tools/call' to invoke one. Completing a checkout requires contemporaneous human approval.", "tags": [ "discovery" ], "security": [ { "ucpSession": [ "checkout:write" ] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "jsonrpc", "method" ], "properties": { "jsonrpc": { "const": "2.0" }, "id": { "type": [ "string", "integer" ] }, "method": { "type": "string", "examples": [ "tools/list" ] }, "params": { "type": "object" } } } } } }, "responses": { "200": { "description": "A JSON-RPC 2.0 result or error.", "content": { "application/json": { "schema": { "type": "object", "required": [ "jsonrpc" ], "properties": { "jsonrpc": { "const": "2.0" }, "id": { "type": [ "string", "integer", "null" ] }, "result": { "type": "object" }, "error": { "$ref": "#/components/schemas/JsonRpcError" } } } } } }, "429": { "$ref": "#/components/responses/RateLimited" } } } } }, "components": { "securitySchemes": { "publicRead": { "type": "oauth2", "description": "Catalog reads are open. No credential is issued or required; the scope is named so agents can reason about least privilege and so tooling can model the surface uniformly.", "flows": { "clientCredentials": { "tokenUrl": "https://heyozzi.com/api/ucp/mcp", "scopes": { "catalog:read": "Read products, variants, prices and availability. Grants no access to customer or order data." } } } }, "sessionCart": { "type": "apiKey", "in": "cookie", "name": "cart", "description": "The cart is scoped to the browser or client session cookie, not to an account. Sending no cookie creates a fresh empty cart. Grants cart:write only; it cannot read customer or order data." }, "ucpSession": { "type": "oauth2", "description": "Transactional scopes for the UCP MCP endpoint. Request the narrowest scope that completes the job. checkout:complete additionally requires contemporaneous human approval and can never be exercised autonomously.", "flows": { "authorizationCode": { "authorizationUrl": "https://heyozzi.com/.well-known/ucp", "tokenUrl": "https://heyozzi.com/api/ucp/mcp", "scopes": { "catalog:read": "Read products, variants, prices and availability.", "cart:write": "Create and modify a cart.", "checkout:write": "Create and advance a checkout, including shipping address and method.", "checkout:complete": "Finalize payment. Requires explicit, contemporaneous buyer approval." } } } } }, "responses": { "NotFound": { "description": "The resource does not exist. Do not retry; re-derive the handle from https://heyozzi.com/sitemap.xml.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "UnprocessableEntity": { "description": "The request was well-formed but rejected — sold out, unknown variant, or a quantity limit. Read `description`, change the request, and do not retry unchanged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "unknownVariant": { "summary": "Variant id does not exist", "value": { "status": 422, "message": "Cart Error", "description": "Cannot find variant" } }, "soldOut": { "summary": "Variant is out of stock", "value": { "status": 422, "message": "Cart Error", "description": "The item you are trying to add is sold out." } }, "missingParameter": { "summary": "A required query parameter was omitted — reproduce with GET /recommendations/products.json", "value": { "status": 422, "message": "Missing parameter error", "description": "A product_id value is missing" } }, "invalidParameter": { "summary": "A required query parameter was omitted — reproduce with GET /search/suggest.json", "value": { "status": 422, "message": "Invalid parameter error", "description": "param is missing or the value is empty or invalid: q" } } } } } }, "RateLimited": { "description": "Rate limited. Back off exponentially with a minimum of 1 second and honour Retry-After.", "headers": { "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }, "schemas": { "Error": { "type": "object", "description": "Structured error returned by the storefront JSON endpoints. Never an HTML page.", "required": [ "status", "message" ], "properties": { "status": { "type": "integer", "description": "The HTTP status code, repeated in the body.", "examples": [ 422 ] }, "message": { "type": "string", "description": "Short machine-stable error class.", "examples": [ "Cart Error" ] }, "description": { "type": "string", "description": "Human-readable explanation and how to resolve it.", "examples": [ "Cannot find variant" ] } } }, "JsonRpcError": { "type": "object", "description": "JSON-RPC 2.0 error returned by the MCP endpoint.", "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "description": "JSON-RPC error code.", "examples": [ -32602 ] }, "message": { "type": "string", "examples": [ "Invalid params" ] }, "data": { "type": "object", "description": "Optional structured detail, such as the offending field." } } }, "Product": { "type": "object", "description": "An Ozzi product. Monetary values are integers in minor units (US cents).", "required": [ "id", "title", "handle", "variants" ], "properties": { "id": { "type": "integer", "format": "int64" }, "title": { "type": "string", "examples": [ "The Crave Crusher v2" ] }, "handle": { "type": "string", "examples": [ "crave-crusher" ] }, "description": { "type": "string", "description": "HTML product description." }, "available": { "type": "boolean", "description": "True when at least one variant is purchasable." }, "price": { "type": "integer", "description": "Lowest variant price in cents.", "examples": [ 5500 ] }, "price_min": { "type": "integer", "description": "Lowest variant price in cents." }, "price_max": { "type": "integer", "description": "Highest variant price in cents." }, "compare_at_price": { "type": [ "integer", "null" ], "description": "Reference price in cents, when on sale." }, "type": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "featured_image": { "type": [ "string", "null" ], "format": "uri" }, "variants": { "type": "array", "items": { "$ref": "#/components/schemas/Variant" } } } }, "Variant": { "type": "object", "description": "A purchasable variant, for example one flavour of the drink stick.", "required": [ "id", "title", "price", "available" ], "properties": { "id": { "type": "integer", "format": "int64", "description": "Pass this as items[].id to addCartItems." }, "title": { "type": "string", "examples": [ "Peach Elderflower" ] }, "sku": { "type": [ "string", "null" ] }, "price": { "type": "integer", "description": "Price in cents.", "examples": [ 6500 ] }, "compare_at_price": { "type": [ "integer", "null" ] }, "available": { "type": "boolean" }, "options": { "type": "array", "items": { "type": "string" } } } }, "Cart": { "type": "object", "description": "The session cart. Monetary values are integers in minor units (US cents).", "required": [ "token", "item_count", "items", "total_price" ], "properties": { "token": { "type": "string", "description": "Opaque cart identifier." }, "item_count": { "type": "integer" }, "total_price": { "type": "integer", "description": "Total in cents." }, "total_discount": { "type": "integer", "description": "Discount applied, in cents." }, "currency": { "type": "string", "examples": [ "USD" ] }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/LineItem" } } } }, "LineItem": { "type": "object", "required": [ "key", "id", "quantity", "line_price" ], "properties": { "key": { "type": "string", "description": "Line identifier. Pass this as id to changeCartItem." }, "id": { "type": "integer", "format": "int64", "description": "The variant id." }, "product_title": { "type": "string" }, "variant_title": { "type": [ "string", "null" ] }, "quantity": { "type": "integer", "minimum": 0 }, "price": { "type": "integer", "description": "Unit price in cents." }, "line_price": { "type": "integer", "description": "Line total in cents." }, "selling_plan_allocation": { "type": [ "object", "null" ], "description": "Present when the line is a subscription." } } } } } }