{
  "openapi": "3.0.3",
  "info": {
    "title": "Printhia public catalog API",
    "version": "1.0.4",
    "description": "Read published Printhia products using the existing Shopify Ajax API. No API key is required. This description covers read operations only. Use locale-prefixed URLs and retain the buyer's market/session context when browsing a localized storefront. Request JSON with Accept: application/json. Empty product 404 responses become RFC 9457 problem details by default and when JSON is preferred. Other Shopify errors retain their native format. UCP commerce tools and their current schemas are discovered separately at /.well-known/ucp and /api/ucp/mcp.",
    "contact": { "name": "Printhia", "url": "https://printhia.sk/pages/contact" }
  },
  "servers": [{ "url": "https://printhia.sk", "description": "Printhia primary storefront" }],
  "externalDocs": { "url": "https://printhia.sk/developers", "description": "Printhia authentication, examples and limitations" },
  "security": [],
  "paths": {
    "/products/{handle}.js": {
      "get": {
        "operationId": "getPrinthiaProduct",
        "summary": "Read a published product",
        "description": "Get current product information and up to 250 variants. Monetary fields are integers in presentment-currency minor units; divide EUR values by 100. Check the currency with getPrinthiaCartContext in the same session. Use the selected variant's price and availability. Product configurators may require additional choices, photos or variants beyond this response.",
        "parameters": [{ "name": "handle", "in": "path", "required": true, "description": "Product handle from search or a product URL.", "schema": { "type": "string", "minLength": 1 }, "example": "vaza-ondra" }],
        "responses": {
          "200": { "description": "Product data encoded as JSON. Shopify serves this .js endpoint as text/javascript.", "content": {
            "text/javascript": { "schema": { "$ref": "#/components/schemas/Product" } },
            "application/json": { "schema": { "$ref": "#/components/schemas/Product" } }
          } },
          "404": { "description": "Product not found or unpublished. For GET requests with no Accept header, a wildcard or a preference for application/json or application/problem+json, an empty or HTML Shopify 404 becomes this problem response. Explicit JSON exclusions and a higher preference for HTML are respected. Existing nonempty JSON errors are preserved. Find a current handle through predictive search or /sitemap.xml.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/NotFoundProblem" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/search/suggest.json": {
      "get": {
        "operationId": "searchPrinthiaProducts",
        "summary": "Search published products",
        "description": "Read predictive product matches and their handles. This is a bounded suggestion list, not a complete paginated catalog. Search prices are decimal strings in major units, unlike the integer amounts in the product .js response. Re-read the selected product and variant before quoting a price.",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "description": "Product search text.", "schema": { "type": "string", "minLength": 1 }, "example": "vaza" },
          { "name": "resources[type]", "in": "query", "required": true, "description": "Restrict suggestions to products.", "schema": { "type": "string", "enum": ["product"] } },
          { "name": "resources[limit]", "in": "query", "description": "Maximum number of product suggestions.", "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 10 } }
        ],
        "responses": {
          "200": { "description": "Product suggestions, possibly an empty list.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchResponse" } } } },
          "417": { "description": "Unsupported buyer locale. Retry using a storefront locale supported by Shopify predictive search.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PredictiveSearchError" } } } },
          "422": { "description": "Invalid or missing parameter. Read description, supply a nonempty q value and set resources[type] to product before retrying.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PredictiveSearchError" }, "example": { "status": 422, "message": "Invalid parameter error", "description": "param is missing or the value is empty: q" } } } },
          "default": { "description": "Shopify error response. Check the HTTP status and content type before parsing; a JSON body is not guaranteed." },
          "429": { "description": "Predictive search request limit exceeded. Slow down requests and honor Retry-After when supplied.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PredictiveSearchError" } } }, "headers": { "Retry-After": { "description": "Optional delay in seconds or HTTP date.", "schema": { "type": "string" } } } }
        }
      }
    },
    "/cart.js": {
      "get": {
        "operationId": "getPrinthiaCartContext",
        "summary": "Read the current session's cart currency",
        "description": "Read the anonymous or buyer session's cart context without adding or changing items. The currency field identifies the presentment currency used by the product .js endpoint. Preserve cookies across requests when working in a buyer's session. Only currency and item count are specified here; Shopify returns additional cart fields.",
        "responses": {
          "200": { "description": "Cart context for this session.", "content": {
            "text/javascript": { "schema": { "$ref": "#/components/schemas/CartContext" } },
            "application/json": { "schema": { "$ref": "#/components/schemas/CartContext" } }
          } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "RateLimited": { "description": "Slow down requests. Honour Retry-After when supplied; otherwise use exponential backoff. The response body is controlled by Shopify and may not be JSON.", "headers": { "Retry-After": { "description": "Optional delay in seconds or HTTP date.", "schema": { "type": "string" } } } }
    },
    "schemas": {
      "NotFoundProblem": {
        "type": "object",
        "description": "RFC 9457 problem details for an unavailable product or API endpoint. The code and resolution members extend the standard format.",
        "required": ["type", "title", "status", "detail", "instance", "code", "resolution"],
        "properties": {
          "type": { "type": "string", "enum": ["https://printhia.sk/developers#not-found"], "description": "Documentation for this problem type." },
          "title": { "type": "string", "enum": ["Not Found"], "description": "Short problem title." },
          "status": { "type": "integer", "enum": [404], "description": "The HTTP response status." },
          "detail": { "type": "string", "minLength": 1, "description": "Explanation of the unavailable resource." },
          "instance": { "type": "string", "pattern": "^/", "description": "Requested path. Query parameters are omitted." },
          "code": { "type": "string", "enum": ["not_found"], "description": "Stable error code." },
          "resolution": { "type": "string", "minLength": 1, "description": "How to find a valid product or endpoint." }
        }
      },
      "PredictiveSearchError": {
        "type": "object",
        "description": "Shopify predictive-search error. Live responses use a numeric status; Shopify documentation also shows string statuses. Use the HTTP status for control flow and description to identify the invalid parameter. These fields are not guaranteed on other endpoints.",
        "required": ["status", "message", "description"],
        "properties": {
          "status": { "description": "HTTP error status.", "oneOf": [{ "type": "integer", "enum": [417, 422, 429] }, { "type": "string", "enum": ["417", "422", "429"] }] },
          "message": { "type": "string", "minLength": 1, "description": "Error category." },
          "description": { "type": "string", "minLength": 1, "description": "Cause of the error. Correct the identified request parameter before retrying a 422 response." }
        }
      },
      "Variant": {
        "type": "object",
        "description": "A product variant. Prices use presentment-currency minor units.",
        "required": ["id", "title", "price", "available", "options"],
        "properties": {
          "id": { "type": "integer", "format": "int64", "description": "Shopify variant ID." },
          "title": { "type": "string", "description": "Variant title." },
          "price": { "type": "integer", "description": "Current price in minor units." },
          "available": { "type": "boolean", "description": "Whether this variant can be bought." },
          "options": { "type": "array", "items": { "type": "string" }, "description": "Selected option values in product option order." }
        }
      },
      "Product": {
        "type": "object",
        "description": "Published product, its current range of prices and a bounded list of variants.",
        "required": ["id", "title", "handle", "price", "price_min", "price_max", "available", "variants"],
        "properties": {
          "id": { "type": "integer", "format": "int64", "description": "Shopify product ID." },
          "title": { "type": "string", "description": "Product name." },
          "handle": { "type": "string", "description": "URL handle." },
          "description": { "type": "string", "nullable": true, "description": "Product description, which can contain HTML." },
          "url": { "type": "string", "description": "Relative product URL." },
          "price": { "type": "integer", "description": "Starting price in minor units, not a quote for every configuration." },
          "price_min": { "type": "integer", "description": "Minimum variant price in minor units." },
          "price_max": { "type": "integer", "description": "Maximum variant price in minor units." },
          "available": { "type": "boolean", "description": "Whether at least one variant is available." },
          "variants": { "type": "array", "maxItems": 250, "items": { "$ref": "#/components/schemas/Variant" } }
        }
      },
      "SearchProduct": {
        "type": "object", "description": "A predictive product match. Monetary strings use major units.",
        "required": ["id", "title", "handle", "url", "available", "price"],
        "properties": {
          "id": { "type": "integer", "format": "int64" },
          "title": { "type": "string" },
          "handle": { "type": "string" },
          "url": { "type": "string", "description": "Product URL, possibly with tracking parameters." },
          "available": { "type": "boolean" },
          "price": { "type": "string", "description": "Decimal amount in presentment-currency major units." }
        }
      },
      "SearchResponse": {
        "type": "object", "required": ["resources"],
        "properties": { "resources": { "type": "object", "required": ["results"], "properties": {
          "results": { "type": "object", "required": ["products"], "properties": { "products": { "type": "array", "items": { "$ref": "#/components/schemas/SearchProduct" } } } }
        } } }
      },
      "CartContext": {
        "type": "object", "required": ["currency", "item_count"],
        "properties": {
          "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "ISO 4217 presentment currency code." },
          "item_count": { "type": "integer", "minimum": 0, "description": "Total item quantity in this session's cart." }
        }
      }
    }
  }
}
