{ "openapi": "3.1.0", "info": { "title": "GentlePup storefront read API", "summary": "Public catalogue and cart-state endpoints for agents acting on behalf of a shopper.", "description": "GentlePup sells dog training and walk essentials, shipping within Australia only, priced in AUD.\n\nThis document covers the **public read surface**: products, collections, search and current cart state. It needs no credentials.\n\nWrites (add to cart, checkout, payment) are NOT described here. Agent commerce goes through the Universal Commerce Protocol: discovery at `/.well-known/ucp`, MCP transport at `/api/ucp/mcp`. Completing a checkout requires explicit buyer approval.\n\nProse facts, FAQs and policies for agents live at `/agents.md`.", "version": "2026-09-13", "contact": { "name": "GentlePup support", "email": "hello@gentlepup.com.au", "url": "https://gentlepup.com.au/pages/contact-us" }, "license": { "name": "Proprietary — catalogue data may be quoted with attribution", "identifier": "LicenseRef-GentlePup-Proprietary" } }, "servers": [{ "url": "https://gentlepup.com.au", "description": "Live storefront" }], "externalDocs": { "description": "Store facts, FAQs and policies in markdown", "url": "https://gentlepup.com.au/agents.md" }, "tags": [ { "name": "catalogue", "description": "Products and collections. No authentication." }, { "name": "search", "description": "Storefront search suggestions. No authentication." }, { "name": "cart", "description": "Read the current session's cart. Session cookie only." } ], "security": [{}], "paths": { "/products/{handle}.json": { "get": { "operationId": "getProduct", "summary": "One product by URL handle", "description": "Returns the product, its variants, options, images and prices. Prices are in AUD, expressed as decimal strings. An unknown handle returns 404.", "tags": ["catalogue"], "security": [{}], "parameters": [ { "name": "handle", "in": "path", "required": true, "description": "The product's URL handle, e.g. `quietcue-ultrasonic-bark-trainer`.", "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" }, "example": "quietcue-ultrasonic-bark-trainer" } ], "responses": { "200": { "description": "The product.", "content": { "application/json": { "schema": { "type": "object", "required": ["product"], "properties": { "product": { "$ref": "#/components/schemas/Product" } } } } } }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/collections/{collection}/products.json": { "get": { "operationId": "listCollectionProducts", "summary": "Products in a collection", "description": "Use `all` as the collection handle for the full catalogue. Paginate with `page`; `limit` caps at 250.", "tags": ["catalogue"], "security": [{}], "parameters": [ { "name": "collection", "in": "path", "required": true, "description": "Collection handle. `all`, `best-sellers` and `bundles` are the published ones.", "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" }, "example": "best-sellers" }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 250, "default": 50 } }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "default": 1 } } ], "responses": { "200": { "description": "A page of products. An unknown collection returns an empty array rather than a 404.", "content": { "application/json": { "schema": { "type": "object", "required": ["products"], "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } } }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/search/suggest.json": { "get": { "operationId": "searchSuggest", "summary": "Search the catalogue", "description": "Type-ahead search across products. Returns matches ranked by relevance.", "tags": ["search"], "security": [{}], "parameters": [ { "name": "q", "in": "query", "required": true, "description": "The search terms.", "schema": { "type": "string", "minLength": 1 }, "example": "bark" }, { "name": "resources[type]", "in": "query", "required": false, "description": "Comma-separated resource types. `product` is the useful one here.", "schema": { "type": "string", "default": "product" } }, { "name": "resources[limit]", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 10 } } ], "responses": { "200": { "description": "Matching products.", "content": { "application/json": { "schema": { "type": "object", "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } } } } } } }, "429": { "$ref": "#/components/responses/RateLimited" } } } }, "/cart.js": { "get": { "operationId": "getCart", "summary": "The current session's cart", "description": "Reads the cart bound to the caller's session cookie. With no cookie this returns an empty cart rather than an error. Served as `text/javascript` by Shopify; the body is JSON.", "tags": ["cart"], "security": [{}], "responses": { "200": { "description": "The cart. `item_count` is 0 for an empty or new session.", "content": { "application/json": { "schema": { "type": "object", "required": ["token", "item_count", "currency"], "properties": { "token": { "type": "string" }, "item_count": { "type": "integer", "minimum": 0 }, "total_price": { "type": "integer", "description": "Cents, AUD." }, "currency": { "type": "string", "example": "AUD" }, "items": { "type": "array", "items": { "type": "object" } } } } } } } } } } }, "components": { "securitySchemes": { "shopifyCustomerOAuth": { "type": "oauth2", "description": "Not required for anything in this document — every path above is public. Declared so an agent that needs a customer-scoped action can discover the authorization server and request least privilege. The same server is named in RFC 9728 metadata at /.well-known/oauth-protected-resource.", "flows": { "authorizationCode": { "authorizationUrl": "https://shopify.com/authentication/99109699870/oauth/authorize", "tokenUrl": "https://shopify.com/authentication/99109699870/oauth/token", "refreshUrl": "https://shopify.com/authentication/99109699870/oauth/token", "scopes": { "openid": "Identify the signed-in customer.", "email": "Read the customer's email address.", "customer-account-api:full": "Read and write the signed-in customer's own account: orders, addresses, profile.", "customer-account-mcp-api:full": "Act on the signed-in customer's account over MCP." } } } } }, "responses": { "NotFound": { "description": "The resource does not exist. Treat as definitive — do not retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "status": 404, "message": "Product not found", "description": "No product with that handle is published in the online store", "resolution": "Check /sitemap.xml or /agents.md for published handles." } } } }, "RateLimited": { "description": "Too many requests. Back off and retry after the interval in the Retry-After header.", "headers": { "Retry-After": { "description": "Seconds to wait.", "schema": { "type": "integer" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "status": 429, "message": "Too many requests", "description": "Rate limit exceeded for this client", "resolution": "Wait for the Retry-After interval, then retry." } } } } }, "schemas": { "Error": { "type": "object", "description": "The error shape this storefront returns. `status`, `message` and `description` are Shopify's; `resolution` is added where GentlePup controls the response, and is a hint for the caller rather than a guarantee.", "required": ["status", "message"], "properties": { "status": { "type": "integer", "description": "The HTTP status, repeated in the body.", "example": 404 }, "message": { "type": "string", "description": "Short machine-stable summary.", "example": "Product not found" }, "description": { "type": "string", "description": "What went wrong, in one sentence." }, "resolution": { "type": "string", "description": "What the caller should do next." } } }, "Product": { "type": "object", "required": ["id", "title", "handle"], "properties": { "id": { "type": "integer", "format": "int64" }, "title": { "type": "string" }, "handle": { "type": "string" }, "body_html": { "type": "string" }, "published_at": { "type": "string", "format": "date-time" }, "vendor": { "type": "string" }, "product_type": { "type": "string" }, "tags": { "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" }, "width": { "type": "integer" }, "height": { "type": "integer" } } } } } }, "Variant": { "type": "object", "required": ["id", "title", "price", "available"], "properties": { "id": { "type": "integer", "format": "int64" }, "title": { "type": "string" }, "price": { "type": "string", "description": "Decimal string, AUD.", "example": "98.90" }, "compare_at_price": { "type": ["string", "null"], "description": "Decimal string, AUD, or null." }, "available": { "type": "boolean" }, "sku": { "type": ["string", "null"] }, "requires_shipping": { "type": "boolean" } } } } } }