{
  "openapi": "3.1.0",
  "info": {
    "title": "SparkBlocks Storefront HTTP API",
    "version": "2026-09-07",
    "summary": "Read-only catalog and cart endpoints for agents browsing or building a cart on sparkblocks.ca.",
    "description": "Machine-readable description of the public HTTP surface of the SparkBlocks storefront.\n\n**Scope.** These are the endpoints an agent can call directly over HTTP without credentials: product and collection catalog reads, product search, and cart construction. They are served by the Shopify platform that hosts this store, so their behaviour is defined by Shopify rather than by SparkBlocks. This document records the behaviour observed on this store and is republished when it changes; treat it as an accurate map, not as a stability guarantee offered by SparkBlocks.\n\n**Checkout is deliberately absent.** No endpoint here places an order. Completing a purchase requires a contemporaneous human approval step, so agents transacting on a buyer's behalf must use the Universal Commerce Protocol endpoint described under `/api/ucp/mcp`, or the Shop skill at https://shop.app/SKILL.md. Both keep payment behind buyer approval. Scripted checkout form-filling is not permitted; see /robots.txt.\n\n**Authentication.** Every endpoint in this document is public and needs no credentials. Authenticated access to a shopper's own account and order history is a separate surface: it uses OAuth 2.0 against Shopify Customer Accounts, whose scopes are declared under `components.securitySchemes.customerAccountOAuth` and served live at /.well-known/oauth-authorization-server. Request the narrowest scope that satisfies the task.\n\n**Rate limits.** The storefront rate-limits aggressive clients. On throttling these endpoints return HTTP 429 with an **HTML** body rather than JSON, which is the one place the API breaks its own JSON error contract. Treat any 429 as a signal to back off exponentially and do not attempt to parse its body.\n\n**Versioning.** The versioned surfaces in this document carry the version in the URL path: `/api/{version}/graphql.json` and `/.well-known/ucp/{version}`. Version strings are calendar-dated (`2026-07`), a new one is published quarterly, and each stays supported for at least twelve months. Every response from a versioned endpoint echoes the version that served it in the `x-shopify-api-version` header, so a client can assert it got what it asked for. Deprecations are signalled per field with an `X-Shopify-API-Deprecated-Reason` response header rather than by removing the endpoint. One caveat worth knowing before you pin a version: an unrecognised or retired version string does **not** return an error, it silently resolves to a supported version, so read `x-shopify-api-version` off the response instead of trusting the string you sent. The unversioned `.js` and `.json` storefront endpoints are not versioned at all; they change only in additive ways in practice, and `info.version` on this document records the last date its description was verified against the store.\n\n**Markdown and content negotiation.** `/llms.txt`, `/agents.md` and `/agent-instructions.md` are served as real `text/markdown`. Do **not** rely on `Accept: text/markdown` against ordinary storefront URLs: the platform answers the home page with a `Content-Type: text/markdown` header and an HTML body, and answers product, collection and page URLs with a bare `404 text/plain`. Fetch the markdown files by path instead. The `<head>` of every page advertises them with `rel=\"alternate\" type=\"text/markdown\"`, alongside `rel=\"service-desc\"` for this document and `rel=\"service-doc\"` for the developer documentation at /pages/developers.",
    "contact": {
      "name": "SparkBlocks Support",
      "email": "info@sparkblocks.ca",
      "url": "https://sparkblocks.ca/pages/help-center"
    },
    "termsOfService": "https://sparkblocks.ca/policies/terms-of-service",
    "license": {
      "name": "Proprietary. This description may be read and cached freely by clients.",
      "url": "https://sparkblocks.ca/policies/terms-of-service"
    },
    "x-agent-instructions": "https://sparkblocks.ca/agent-instructions.md",
    "x-ucp-discovery": "https://sparkblocks.ca/.well-known/ucp",
    "x-agent-instructions-mirrors": [
      "https://sparkblocks.ca/llms.txt",
      "https://sparkblocks.ca/agents.md"
    ],
    "x-developer-documentation": "https://sparkblocks.ca/pages/developers",
    "x-pricing": "https://sparkblocks.ca/pages/pricing",
    "x-api-versioning": {
      "strategy": "url-path",
      "versionedPaths": [
        "/api/{version}/graphql.json",
        "/.well-known/ucp/{version}"
      ],
      "format": "YYYY-MM, calendar-dated",
      "current": "2026-07",
      "releaseCadence": "quarterly",
      "supportWindow": "at least 12 months per version",
      "versionEchoHeader": "x-shopify-api-version",
      "deprecationHeader": "X-Shopify-API-Deprecated-Reason",
      "unknownVersionBehaviour": "An unrecognised version string resolves to a supported version and returns 200 rather than 400. Verified on this store: POST /api/2019-04/graphql.json answers 200. Read the x-shopify-api-version response header to learn which version actually served the call.",
      "unversionedPaths": "The .js and .json storefront endpoints in this document are not versioned. Breaking changes to them are announced in Shopify's changelog, not through this document.",
      "policyUrl": "https://sparkblocks.ca/pages/developers#versioning"
    },
    "x-rate-limiting": {
      "summary": "The storefront does not emit RFC 9331 RateLimit headers. Verified 2026-09-07 against GET /products.json, GET /cart.js and POST /api/2026-07/graphql.json: no RateLimit, RateLimit-Policy or Retry-After header is present on a 2xx. The signals that do exist are listed below. Budget client-side rather than waiting to be told.",
      "recommendedClientBudget": "at most 4 requests per second per IP, serially where possible",
      "backoff": "exponential, starting at 2s, on any 429 or 5xx",
      "throttleSignals": [
        {
          "header": "shopify-complexity-score-v2",
          "appliesTo": "POST /api/{version}/graphql.json",
          "meaning": "Cost of the query just executed. Rises with query depth and page size."
        },
        {
          "header": "shopify-complexity-score",
          "appliesTo": "POST /api/{version}/graphql.json",
          "meaning": "Legacy form of the same signal."
        },
        {
          "bodyField": "extensions.cost.requestedQueryCost",
          "appliesTo": "POST /api/{version}/graphql.json",
          "meaning": "Cost the server charged for the request, in the response body."
        }
      ],
      "throttledResponse": "HTTP 429 with an HTML body, not JSON. This is the one place the storefront breaks its own JSON error contract; do not parse the body.",
      "policyUrl": "https://sparkblocks.ca/pages/developers#rate-limits"
    }
  },
  "externalDocs": {
    "description": "SparkBlocks developer and agent documentation: endpoints, error format, versioning, rate limits and the command line client.",
    "url": "https://sparkblocks.ca/pages/developers"
  },
  "servers": [
    {
      "url": "https://sparkblocks.ca",
      "description": "Primary storefront domain."
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Catalog",
      "description": "Read product and collection data. No authentication."
    },
    {
      "name": "Cart",
      "description": "Build a cart. No authentication. Cannot place an order."
    },
    {
      "name": "Discovery",
      "description": "Machine-readable descriptions of this store."
    },
    {
      "name": "Agent commerce",
      "description": "Buyer-approved checkout over UCP/MCP."
    },
    {
      "name": "Customer account",
      "description": "Acting on behalf of a signed-in shopper. Requires OAuth 2.0 and a declared scope."
    },
    {
      "name": "Storefront GraphQL",
      "description": "Versioned public GraphQL API. No authentication needed from the storefront domain."
    }
  ],
  "paths": {
    "/products.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listProducts",
        "summary": "List published products",
        "description": "Returns published products in the store's default order. Paginate with `limit` and `page`; there is no cursor. An empty `products` array means you have paged past the end.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of products.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/products/{handle}.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getProduct",
        "summary": "Get one product by handle",
        "description": "Returns a single product. The handle is the last path segment of the product's page URL (`/products/{handle}`). Do not guess handles; resolve them with `listProducts` or `suggest`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Handle"
          }
        ],
        "responses": {
          "200": {
            "description": "The product.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No published product has this handle. Note that this response carries an **empty body** despite its `application/json` content type, so branch on the status code rather than attempting to parse it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "maxLength": 0
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/collections.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listCollections",
        "summary": "List published collections",
        "description": "Every published collection with its handle and product count. Call this once to learn the handles, then read a range with /collections/{handle}/products.json instead of paging the whole catalog.",
        "security": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published collections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/collections/{handle}/products.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listCollectionProducts",
        "summary": "List products in a collection",
        "description": "Returns published products in one collection. Use the handle `all` for the full catalog.\n\n**Caution:** an unknown collection handle returns HTTP 200 with `{\"products\": []}`, not a 404. An empty array therefore means either \"this collection is empty\" or \"this collection does not exist\", and the two are indistinguishable from this endpoint alone. Confirm the handle against `/collections.json` before concluding a collection is empty.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Handle"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of products, or an empty array if the collection is empty or absent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/recommendations/products.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "productRecommendations",
        "summary": "Products related to one product",
        "description": "The store's own related-product list for a given product id. Useful for answering \"what goes with this?\" without inventing a pairing. Note the parameter is a **product** id, not a variant id.",
        "security": [],
        "parameters": [
          {
            "name": "product_id",
            "in": "query",
            "required": true,
            "description": "Product id, from `Product.id`.",
            "schema": {
              "type": "integer",
              "format": "int64",
              "examples": [
                9965947093313
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 4
            }
          },
          {
            "name": "intent",
            "in": "query",
            "required": false,
            "description": "`related` for similar products, `complementary` for add-ons.",
            "schema": {
              "type": "string",
              "enum": [
                "related",
                "complementary"
              ],
              "default": "related"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recommended products, plus the intent that was applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecommendationList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/search/suggest.json": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "suggest",
        "summary": "Search products, collections, pages and articles",
        "description": "Predictive search across the storefront. This is the intended way to turn a shopper's words into product handles.",
        "security": [],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The search term.",
            "schema": {
              "type": "string"
            },
            "example": "circuit kit"
          },
          {
            "name": "resources[type]",
            "in": "query",
            "required": false,
            "description": "Comma-separated resource types to search.",
            "schema": {
              "type": "string",
              "default": "product"
            },
            "example": "product,collection,article"
          },
          {
            "name": "resources[limit]",
            "in": "query",
            "required": false,
            "description": "Results per resource type.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Grouped search results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuggestResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/{version}/graphql.json": {
      "post": {
        "tags": [
          "Storefront GraphQL"
        ],
        "operationId": "storefrontGraphql",
        "summary": "Storefront GraphQL API (versioned, public)",
        "description": "The Storefront GraphQL API, reachable without an access token when called on this store's own domain. This is the richest read surface here and the one to reach for when the flat `.json` endpoints would mean several round trips: it filters, sorts and paginates in one call, and returns only the fields asked for.\n\nThe version sits in the path, so a client pins the contract it was written against. GraphQL reports its own failures with HTTP 200 and an `errors` array, so check the body rather than the status. Read `shopify-complexity-score-v2` or `extensions.cost` to keep query cost in view.\n\nWrite operations (cart and checkout mutations) are out of scope for this document; use the UCP endpoint under /api/ucp/mcp, which keeps payment behind buyer approval.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GraphqlRequest"
              },
              "examples": {
                "catalogWithPrices": {
                  "summary": "First 10 products with price and availability",
                  "value": {
                    "query": "{ products(first: 10) { edges { node { handle title availableForSale priceRange { minVariantPrice { amount currencyCode } } } } } }"
                  }
                },
                "oneProduct": {
                  "summary": "One product by handle, with its variants",
                  "value": {
                    "query": "query P($handle: String!) { productByHandle(handle: $handle) { title variants(first: 10) { edges { node { id title availableForSale price { amount currencyCode } } } } } }",
                    "variables": {
                      "handle": "sparkblocks-intro-kit"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A GraphQL response. Present even for a failed operation, with the failure in `errors`.",
            "headers": {
              "x-shopify-api-version": {
                "description": "The API version that actually served this request.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "2026-07"
                  ]
                }
              },
              "shopify-complexity-score-v2": {
                "description": "Cost charged for this query. The storefront emits no RFC 9331 RateLimit headers; this and `extensions.cost` in the body are the throttle signals that exist.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphqlResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cart.js": {
      "get": {
        "tags": [
          "Cart"
        ],
        "operationId": "getCart",
        "summary": "Get the current cart",
        "description": "Returns the cart bound to the caller's session cookie. A client that does not persist cookies receives a new empty cart on every call.\n\nResponses carry the content type `text/javascript`, not `application/json`; the body is still JSON. Parse by content, not by header.",
        "security": [],
        "responses": {
          "200": {
            "description": "The current cart.",
            "content": {
              "text/javascript": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cart/add.js": {
      "post": {
        "tags": [
          "Cart"
        ],
        "operationId": "addToCart",
        "summary": "Add line items to the cart",
        "description": "Adds one or more variants. `id` is a **variant** id, not a product id; read it from `Product.variants[].id`. Adding to a cart never charges anyone and never places an order.",
        "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",
                          "format": "int64",
                          "description": "Variant id."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "properties": {
                          "type": "object",
                          "description": "Free-form line item properties.",
                          "additionalProperties": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "items": [
                  {
                    "id": 50583959503169,
                    "quantity": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The added line items.",
            "content": {
              "text/javascript": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "422": {
            "description": "The cart operation was rejected. The body is JSON and carries a machine-readable `status`, a short `message` naming the error class, and a `description` explaining how to correct the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartError"
                },
                "examples": {
                  "unknownVariant": {
                    "summary": "The variant id does not exist on this store",
                    "value": {
                      "status": 422,
                      "message": "Cart Error",
                      "description": "Cannot find variant"
                    }
                  },
                  "missingParameter": {
                    "summary": "A required parameter was omitted",
                    "value": {
                      "status": 400,
                      "message": "Parameter Missing or Invalid",
                      "description": "Required parameter missing or invalid: 'line' or 'id' param is required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cart/change.js": {
      "post": {
        "tags": [
          "Cart"
        ],
        "operationId": "changeCartLine",
        "summary": "Change the quantity of one cart line",
        "description": "Adjusts a single existing line. Identify the line by its 1-based `line` index or by its `id` (the line item key from `Cart.items[].key`). Setting `quantity` to 0 removes the line.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "quantity"
                ],
                "properties": {
                  "line": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "1-based index of the line."
                  },
                  "id": {
                    "type": "string",
                    "description": "Line item key, as an alternative to `line`."
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "anyOf": [
                  {
                    "required": [
                      "line"
                    ]
                  },
                  {
                    "required": [
                      "id"
                    ]
                  }
                ]
              },
              "example": {
                "line": 1,
                "quantity": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated cart.",
            "content": {
              "text/javascript": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "400": {
            "description": "The cart operation was rejected. The body is JSON and carries a machine-readable `status`, a short `message` naming the error class, and a `description` explaining how to correct the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartError"
                },
                "examples": {
                  "unknownVariant": {
                    "summary": "The variant id does not exist on this store",
                    "value": {
                      "status": 422,
                      "message": "Cart Error",
                      "description": "Cannot find variant"
                    }
                  },
                  "missingParameter": {
                    "summary": "A required parameter was omitted",
                    "value": {
                      "status": 400,
                      "message": "Parameter Missing or Invalid",
                      "description": "Required parameter missing or invalid: 'line' or 'id' param is required"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The cart operation was rejected. The body is JSON and carries a machine-readable `status`, a short `message` naming the error class, and a `description` explaining how to correct the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartError"
                },
                "examples": {
                  "unknownVariant": {
                    "summary": "The variant id does not exist on this store",
                    "value": {
                      "status": 422,
                      "message": "Cart Error",
                      "description": "Cannot find variant"
                    }
                  },
                  "missingParameter": {
                    "summary": "A required parameter was omitted",
                    "value": {
                      "status": 400,
                      "message": "Parameter Missing or Invalid",
                      "description": "Required parameter missing or invalid: 'line' or 'id' param is required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cart/update.js": {
      "post": {
        "tags": [
          "Cart"
        ],
        "operationId": "updateCart",
        "summary": "Update several cart lines, the note, or cart attributes",
        "description": "Bulk counterpart to `changeCartLine`. Keys of `updates` are variant ids and values are absolute quantities, so a value of 0 removes that variant entirely.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "updates": {
                    "type": "object",
                    "description": "Variant id to absolute quantity.",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "updates": {
                  "50583959503169": 2
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated cart.",
            "content": {
              "text/javascript": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "422": {
            "description": "The cart operation was rejected. The body is JSON and carries a machine-readable `status`, a short `message` naming the error class, and a `description` explaining how to correct the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartError"
                },
                "examples": {
                  "unknownVariant": {
                    "summary": "The variant id does not exist on this store",
                    "value": {
                      "status": 422,
                      "message": "Cart Error",
                      "description": "Cannot find variant"
                    }
                  },
                  "missingParameter": {
                    "summary": "A required parameter was omitted",
                    "value": {
                      "status": 400,
                      "message": "Parameter Missing or Invalid",
                      "description": "Required parameter missing or invalid: 'line' or 'id' param is required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cart/clear.js": {
      "post": {
        "tags": [
          "Cart"
        ],
        "operationId": "clearCart",
        "summary": "Remove every line from the cart",
        "security": [],
        "responses": {
          "200": {
            "description": "The now-empty cart.",
            "content": {
              "text/javascript": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/.well-known/ucp": {
      "get": {
        "tags": [
          "Agent commerce",
          "Discovery"
        ],
        "operationId": "getUcpDiscovery",
        "summary": "Universal Commerce Protocol discovery document",
        "description": "Declares the protocol versions, service endpoints, capabilities and payment handlers this store supports. Start here for anything transactional.",
        "security": [],
        "responses": {
          "200": {
            "description": "The UCP merchant profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UcpDiscovery"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/.well-known/ucp/{version}": {
      "get": {
        "tags": [
          "Agent commerce",
          "Discovery"
        ],
        "operationId": "getUcpDiscoveryVersion",
        "summary": "UCP discovery for one protocol version",
        "description": "The merchant profile as it stands under one specific UCP version, for a client pinned to an older protocol. Take the version strings from `ucp.supported_versions` in GET /.well-known/ucp; do not guess them.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/UcpVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The merchant profile for the requested version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UcpDiscovery"
                }
              }
            }
          },
          "404": {
            "description": "That protocol version is not supported by this store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/ucp/mcp": {
      "post": {
        "tags": [
          "Agent commerce"
        ],
        "operationId": "ucpMcp",
        "summary": "UCP commerce endpoint (JSON-RPC 2.0 over MCP)",
        "description": "The transactional surface. Call `tools/list` to enumerate the available tools and their JSON schemas; at the time of writing they cover catalog search and lookup, cart construction, checkout and order retrieval.\n\nPayment still requires explicit, contemporaneous buyer approval. An agent that cannot obtain that approval at the moment of payment must not complete a checkout here.\n\nErrors are returned as JSON-RPC error objects with a numeric `code`, a human-readable `message`, and a `data` object that frequently carries a `continue_url` an agent can hand back to the buyer.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC result, or a JSON-RPC error object with HTTP 200.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/JsonRpcResult"
                    },
                    {
                      "$ref": "#/components/schemas/JsonRpcErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, for example a body that is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Discovery",
          "Customer account"
        ],
        "operationId": "getOAuthAuthorizationServerMetadata",
        "summary": "OAuth 2.0 authorization server metadata (RFC 8414)",
        "description": "Live authorization server metadata for the customer account surface. Prefer this over the endpoints hardcoded in `components.securitySchemes.customerAccountOAuth`, which are a snapshot. `code_challenge_methods_supported` confirms PKCE with S256.",
        "security": [],
        "responses": {
          "200": {
            "description": "Authorization server metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthAuthorizationServerMetadata"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "Discovery",
          "Customer account"
        ],
        "operationId": "getOAuthProtectedResourceMetadata",
        "summary": "OAuth 2.0 protected resource metadata (RFC 9728)",
        "description": "Names this storefront as a protected resource and lists the authorization servers that can issue tokens for it. Read it before starting an authorization code flow so the token is requested from a server this resource actually trusts.",
        "security": [],
        "responses": {
          "200": {
            "description": "Protected resource metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthProtectedResourceMetadata"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/account/customer/api/{version}/graphql": {
      "servers": [
        {
          "url": "https://shopify.com/93593305409",
          "description": "Shopify Customer Accounts host for this store. Not on the storefront domain."
        }
      ],
      "post": {
        "tags": [
          "Customer account"
        ],
        "operationId": "customerAccountGraphql",
        "summary": "Customer Account API (GraphQL)",
        "description": "Reads and writes the signed-in shopper's own account: their orders, addresses and profile. Requires a bearer token obtained through the `customerAccountOAuth` flow.\n\n`version` is a Shopify API version such as `2026-07`. This endpoint answers 401 before it validates the version, so an unauthenticated probe cannot be used to discover which versions are live; consult https://shopify.dev/docs/api/customer for the supported list.\n\nErrors use the GraphQL error envelope, `{\"errors\": [{\"message\": ...}]}`, served with the content type `application/graphql-response+json`.",
        "security": [
          {
            "customerAccountOAuth": [
              "customer-account-api:full"
            ]
          }
        ],
        "parameters": [
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "Shopify API version.",
            "schema": {
              "type": "string",
              "pattern": "^(\\d{4}-\\d{2}|unstable)$"
            },
            "example": "2026-07"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string"
                  },
                  "variables": {
                    "type": "object"
                  },
                  "operationName": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "query": "{ customer { id firstName } }"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A GraphQL response.",
            "content": {
              "application/graphql-response+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or a token lacking the required scope. Obtain one through the `customerAccountOAuth` flow and retry with `Authorization: Bearer <token>`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphqlErrors"
                },
                "example": {
                  "errors": [
                    {
                      "message": "Unauthorized"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/account/customer/api/mcp": {
      "servers": [
        {
          "url": "https://shopify.com/93593305409",
          "description": "Shopify Customer Accounts host for this store. Not on the storefront domain."
        }
      ],
      "post": {
        "tags": [
          "Customer account",
          "Agent commerce"
        ],
        "operationId": "customerAccountMcp",
        "summary": "Customer Account MCP endpoint (JSON-RPC 2.0)",
        "description": "Exposes the signed-in shopper's account as MCP tools, for example order-status lookups.\n\n`tools/list` is public, so an agent can discover the tool surface before asking the shopper to authorize anything. Every `tools/call` requires a bearer token carrying the `customer-account-mcp-api:full` scope and returns HTTP 401 with `{\"errors\": [{\"message\": \"Unauthorized\"}]}` without one.",
        "security": [
          {
            "customerAccountOAuth": [
              "customer-account-mcp-api:full"
            ]
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC result. `tools/list` succeeds unauthenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResult"
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or a token lacking the required scope. Obtain one through the `customerAccountOAuth` flow and retry with `Authorization: Bearer <token>`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphqlErrors"
                },
                "example": {
                  "errors": [
                    {
                      "message": "Unauthorized"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agent-instructions.md": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getAgentInstructions",
        "summary": "When to use this store, and how",
        "description": "Markdown written for agents: the jobs this store is a good answer for, the jobs it is not, and the call to make for each one. The same material as `x-agent-guidance` in this document, in prose. `/llms.txt` and `/agents.md` are platform-generated companions that describe the protocols; this file describes the fit.",
        "security": [],
        "responses": {
          "200": {
            "description": "Agent instructions as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/cli.js": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getCli",
        "summary": "Official command line client",
        "description": "A single-file client for the read endpoints in this document, with no dependencies and no install step. Needs Node 18 or newer for its global fetch.\n\n    curl -sO https://sparkblocks.ca/cli.js\n    node cli.js discovery\n\nServed from the store rather than a package registry, so there is no third party in the supply chain. Data goes to stdout as JSON and diagnostics to stderr; exit code is 0 on success, 1 on a usage error and 2 on a transport or API error. The path is a 301 to the theme asset that holds the file, so follow redirects.",
        "security": [],
        "responses": {
          "200": {
            "description": "The client source.",
            "content": {
              "text/javascript": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getLlmsTxt",
        "summary": "Agent instructions for this store",
        "description": "Markdown guidance for agents. Mirrored at `/agents.md`.",
        "security": [],
        "responses": {
          "200": {
            "description": "Agent instructions.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agents.md": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getAgentsMd",
        "summary": "Platform-generated agent instructions",
        "description": "Byte-identical to /llms.txt on this store. Generated by the platform, so it stays current with the commerce protocols without being republished by hand. For the store-specific \"when to use this\" guidance, read /agent-instructions.md instead.",
        "security": [],
        "responses": {
          "200": {
            "description": "Agent instructions as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getOpenapiDocument",
        "summary": "This document",
        "description": "This OpenAPI description, so a client that found the store some other way can pick it up from a self-describing link. Also advertised from the `<head>` of every page as `<link rel=\"service-desc\" type=\"application/json\">`. `info.version` is the date the document was last verified against the store.\n\nThe path is a 301 to the theme asset that holds the file, because the platform owns the root namespace and a theme cannot serve `/openapi.json` directly. Follow redirects.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 description of this storefront.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getSitemap",
        "summary": "Sitemap index",
        "description": "Index of nested sitemaps covering products, collections, pages and blog articles. This is the authoritative list of crawlable URLs.",
        "security": [],
        "responses": {
          "200": {
            "description": "A sitemap index document.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "customerAccountOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.0 against Shopify Customer Accounts, for agents acting on behalf of a signed-in shopper (order history, saved addresses). None of the endpoints in this document require it; it is declared here so an agent can request least-privilege access before reaching for the Customer Account API. Authoritative metadata is served at https://sparkblocks.ca/.well-known/oauth-authorization-server (RFC 8414) and https://sparkblocks.ca/.well-known/oauth-protected-resource (RFC 9728). PKCE with S256 is required.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://account.sparkblocks.ca/authentication/oauth/authorize",
            "tokenUrl": "https://account.sparkblocks.ca/authentication/oauth/token",
            "refreshUrl": "https://account.sparkblocks.ca/authentication/oauth/token",
            "scopes": {
              "openid": "Confirm the shopper's identity and receive an ID token. Grants no access to store data.",
              "email": "Read the shopper's email address.",
              "customer-account-api:full": "Full read and write access to the signed-in shopper's own account through the Customer Account API, including their orders, addresses and profile. Broad; request only when the task genuinely needs order or address data.",
              "customer-account-mcp-api:full": "Full access to the shopper's account through the Customer Account MCP endpoint. Equivalent reach to customer-account-api:full, exposed as MCP tools."
            }
          }
        }
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Products per page.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 250,
          "default": 30
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "1-based page number.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "Handle": {
        "name": "handle",
        "in": "path",
        "required": true,
        "description": "URL slug of the resource.",
        "schema": {
          "type": "string"
        },
        "example": "master-builder-bundle"
      },
      "ApiVersion": {
        "name": "version",
        "in": "path",
        "required": true,
        "description": "Calendar-dated API version, for example `2026-07`. A new version is published quarterly and each is supported for at least twelve months. An unrecognised string resolves to a supported version rather than failing, so read the `x-shopify-api-version` response header to confirm which version served the call.",
        "schema": {
          "type": "string",
          "pattern": "^(\\d{4}-\\d{2}|unstable)$",
          "default": "2026-07",
          "examples": [
            "2026-07",
            "2026-04"
          ]
        }
      },
      "UcpVersion": {
        "name": "version",
        "in": "path",
        "required": true,
        "description": "A UCP protocol version this store supports. Read the list from `ucp.supported_versions` in GET /.well-known/ucp rather than hardcoding one.",
        "schema": {
          "type": "string",
          "examples": [
            "2026-04-08",
            "2026-01-23"
          ]
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Too many requests. **This response is HTML, not JSON.** Back off exponentially and retry; do not parse the body.",
        "content": {
          "text/html": {
            "schema": {
              "type": "string"
            }
          }
        }
      }
    },
    "schemas": {
      "CartError": {
        "type": "object",
        "description": "The storefront's structured error envelope for cart operations.",
        "required": [
          "status",
          "message",
          "description"
        ],
        "properties": {
          "status": {
            "type": "integer",
            "description": "Machine-readable status code for the failure."
          },
          "message": {
            "type": "string",
            "description": "Short error class, for example `Cart Error`."
          },
          "description": {
            "type": "string",
            "description": "Human-readable explanation and resolution hint."
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "id",
          "title",
          "handle",
          "variants"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "handle": {
            "type": "string",
            "description": "URL slug; the product page is `/products/{handle}`."
          },
          "body_html": {
            "type": "string",
            "description": "Product description as HTML."
          },
          "published_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_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": {
              "$ref": "#/components/schemas/Image"
            }
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Option"
            }
          }
        }
      },
      "Variant": {
        "type": "object",
        "description": "A purchasable variant. `id` is what `addToCart` expects.",
        "required": [
          "id",
          "title",
          "price"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "sku": {
            "type": [
              "string",
              "null"
            ]
          },
          "price": {
            "type": "string",
            "description": "Decimal string in the store's currency, for example `945.00`."
          },
          "compare_at_price": {
            "type": [
              "string",
              "null"
            ]
          },
          "available": {
            "type": "boolean",
            "description": "Whether this variant can currently be purchased."
          },
          "requires_shipping": {
            "type": "boolean"
          },
          "taxable": {
            "type": "boolean"
          },
          "featured_image": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Image"
              },
              {
                "type": "null"
              }
            ]
          },
          "option1": {
            "type": [
              "string",
              "null"
            ]
          },
          "option2": {
            "type": [
              "string",
              "null"
            ]
          },
          "option3": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Image": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "src": {
            "type": "string",
            "format": "uri"
          },
          "alt": {
            "type": [
              "string",
              "null"
            ]
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "Option": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Cart": {
        "type": "object",
        "description": "Prices are integers in the currency's minor unit, so 94500 means 945.00.",
        "properties": {
          "token": {
            "type": "string"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "item_count": {
            "type": "integer"
          },
          "total_price": {
            "type": "integer",
            "description": "Total in minor units."
          },
          "original_total_price": {
            "type": "integer"
          },
          "total_discount": {
            "type": "integer"
          },
          "items_subtotal_price": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code."
          },
          "requires_shipping": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartItem"
            }
          }
        }
      },
      "CartItem": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Line item key; accepted as `id` by `changeCartLine`."
          },
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Variant id."
          },
          "product_id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "price": {
            "type": "integer",
            "description": "Unit price in minor units."
          },
          "line_price": {
            "type": "integer"
          },
          "handle": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "properties": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "SuggestResponse": {
        "type": "object",
        "properties": {
          "resources": {
            "type": "object",
            "properties": {
              "results": {
                "type": "object",
                "properties": {
                  "products": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Product"
                    }
                  },
                  "collections": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "pages": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "articles": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "UcpDiscovery": {
        "type": "object",
        "description": "See https://ucp.dev for the authoritative schema.",
        "properties": {
          "ucp": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "supported_versions": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "services": {
                "type": "object"
              },
              "capabilities": {
                "type": "object"
              },
              "payment_handlers": {
                "type": "object"
              }
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "method": {
            "type": "string",
            "example": "tools/list"
          },
          "params": {
            "type": "object"
          }
        }
      },
      "JsonRpcResult": {
        "type": "object",
        "required": [
          "jsonrpc",
          "result"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object"
          }
        }
      },
      "JsonRpcErrorResponse": {
        "type": "object",
        "required": [
          "jsonrpc",
          "error"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC error code."
              },
              "message": {
                "type": "string"
              },
              "data": {
                "type": "object",
                "description": "Additional context. Often carries a `code` string and a `continue_url` to hand back to the buyer.",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "continue_url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        }
      },
      "GraphqlErrors": {
        "type": "object",
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "message"
              ],
              "properties": {
                "message": {
                  "type": "string"
                },
                "extensions": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "CollectionSummary": {
        "type": "object",
        "description": "A collection as returned by the unauthenticated collections listing.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "handle": {
            "type": "string",
            "description": "Use this in /collections/{handle}/products.json."
          },
          "description": {
            "type": "string",
            "description": "HTML. Empty string when unset."
          },
          "published_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "image": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Image"
              },
              {
                "type": "null"
              }
            ]
          },
          "products_count": {
            "type": "integer"
          }
        }
      },
      "CollectionList": {
        "type": "object",
        "required": [
          "collections"
        ],
        "properties": {
          "collections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CollectionSummary"
            }
          }
        }
      },
      "RecommendationList": {
        "type": "object",
        "required": [
          "products"
        ],
        "properties": {
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          },
          "intent": {
            "type": "string",
            "description": "Echoes the requested intent, for example `related`.",
            "enum": [
              "related",
              "complementary"
            ]
          }
        }
      },
      "GraphqlRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The GraphQL document."
          },
          "variables": {
            "type": "object",
            "additionalProperties": true
          },
          "operationName": {
            "type": "string"
          }
        }
      },
      "GraphqlResponse": {
        "type": "object",
        "description": "A GraphQL response. `errors` is present when any part of the operation failed, and can appear alongside partial `data`; always inspect it rather than only checking the HTTP status, which is 200 for a query that failed validation.",
        "properties": {
          "data": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "message"
              ],
              "properties": {
                "message": {
                  "type": "string"
                },
                "locations": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "path": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "extensions": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "extensions": {
            "type": "object",
            "description": "Carries the cost the server charged for this request.",
            "properties": {
              "cost": {
                "type": "object",
                "properties": {
                  "requestedQueryCost": {
                    "type": "integer"
                  },
                  "actualQueryCost": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "OAuthAuthorizationServerMetadata": {
        "type": "object",
        "description": "RFC 8414 authorization server metadata.",
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint"
        ],
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "jwks_uri": {
            "type": "string",
            "format": "uri"
          },
          "end_session_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "S256 is required on this store."
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "id_token_signing_alg_values_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "claims_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ui_locales_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "op_policy_uri": {
            "type": "string",
            "format": "uri"
          },
          "op_tos_uri": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "OAuthProtectedResourceMetadata": {
        "type": "object",
        "description": "RFC 9728 protected resource metadata.",
        "required": [
          "resource",
          "authorization_servers"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  },
  "x-agent-guidance": {
    "oneLine": "SparkBlocks sells snap-together electronic circuit blocks that clip onto the plastic bricks a child already owns. A block adds a light, a motor or a sound to a build, and none of it involves a screen, a loose wire or a soldering iron. Designed for ages 8 and up, with a sweet spot around 8 to 12.",
    "useThisStoreWhen": [
      "A shopper wants a hands-on STEM or electronics gift for a child of about 8 to 12, especially one who already builds with plastic bricks.",
      "A shopper asks for screen-free, no-soldering electronics kits.",
      "A teacher, librarian or camp organiser needs a classroom set several children can share, with printable lesson material. Start at /collections/for-educators and the Smart Classroom Kit.",
      "Someone owns a SparkBlocks kit and needs build instructions, a manual or a replacement part. Start at /pages/help-center and the /blogs/help articles.",
      "You need current prices, stock or shipping availability for a SparkBlocks product, in which case read it from the API rather than from a cached page."
    ],
    "doNotUseThisStoreFor": [
      "Bare components, breadboards, microcontrollers or anything aimed at adult hobby electronics. The kits are closed-system and brick-compatible by design.",
      "Anything cheap as a standalone purchase. The lowest price in the catalog is 59 on the base USD list, and the classroom kit is 1,399.",
      "Placing an order without the buyer present. Checkout requires contemporaneous human approval; see the checkout note in info.description."
    ],
    "howToCall": {
      "browseCatalog": "GET /products.json?limit=250 for the whole catalog in one call, or GET /collections/{handle}/products.json for one range.",
      "answerAQuestionAboutOneProduct": "GET /products/{handle}.json. Handles are listed in /sitemap_products_1.xml.",
      "findByKeyword": "GET /search/suggest.json?q=QUERY&resources[type]=product",
      "richerQueries": "POST /api/2026-07/graphql.json with a GraphQL body. Public, no token needed from this domain, and the only surface that can filter, sort and paginate in one round trip.",
      "prices": "Read them from the API or /pages/pricing, and read the currency with them. This storefront prices in the market the request resolves to, which is CAD by default on sparkblocks.ca, while the shop's base price list is USD and ten presentment currencies are enabled. Every price in the JSON carries a price_currency field beside it; use it rather than assuming a currency, and do not quote a cached figure.",
      "buildACart": "POST /cart/add.js with variant ids. This charges nobody.",
      "transact": "Use the UCP MCP endpoint POST /api/ucp/mcp, discovered through GET /.well-known/ucp. Payment stays behind buyer approval."
    },
    "identify": "Send a descriptive User-Agent with a contact URL. Unidentified bursts are throttled first."
  }
}
