{
  "openapi": "3.1.0",
  "info": {
    "title": "Quota Vita Storefront & Commerce API",
    "version": "1.2.1",
    "summary": "Machine-readable description of every public endpoint an AI agent can call on www.quotavita.com.",
    "description": "Quota Vita is a Catalan sports-nutrition brand selling whey protein and creatine, running on Shopify.\n\nThis document describes the store's agent-facing surface:\n\n1. **Universal Commerce Protocol (UCP)** over MCP — discovery, catalog search, cart and checkout (`POST /api/ucp/mcp`).\n2. **Storefront GraphQL API** — full catalog, cart and checkout access (`POST /api/{version}/graphql.json`).\n3. **Read-only JSON endpoints** — products, collections and predictive search, no authentication required.\n4. **Ajax Cart API** — cart mutation for browser and agent sessions.\n5. **Agent discovery documents** — `/agents.md`, `/llms.txt`, `/.well-known/ucp`, `/sitemap.xml`.\n\n### Error handling\n\nEvery endpoint in this document returns a structured JSON error body. There are three\nerror envelopes, one per protocol family; all three carry a machine-readable code, a\nhuman-readable message and a resolution hint:\n\n| Surface | Envelope | Schema |\n| --- | --- | --- |\n| UCP / MCP | JSON-RPC 2.0 error object | `JsonRpcErrorResponse` |\n| Storefront GraphQL | GraphQL `errors` array | `GraphQLErrorResponse` |\n| Ajax Cart & Storefront JSON | Shopify cart error object | `CartError` |\n\nAgents must never parse an HTML body to determine failure. If a response's\n`Content-Type` is `text/html`, treat it as an unexpected transport-level failure,\nnot as an API error, and retry against the documented JSON endpoint.\n\n### Recovering from a 404\n\nNonexistent storefront paths return a real HTTP 404 whose body links back to\n`/sitemap.xml`, `/agents.md`, `/llms.txt`, `/openapi.json` and `/collections/all`.\nUse those to re-orient rather than guessing further paths.\n\n### Rate limits\n\nThe MCP endpoint and the Storefront GraphQL API are rate limited per IP and per token.\nBack off on `429` and honour the `Retry-After` header when present.",
    "termsOfService": "https://www.quotavita.com/policies/terms-of-service",
    "contact": {
      "name": "Quota Vita",
      "email": "hi@quotavita.com",
      "url": "https://www.quotavita.com"
    },
    "license": {
      "name": "Proprietary — API description published for interoperability",
      "identifier": "LicenseRef-QuotaVita-API-Docs"
    }
  },
  "externalDocs": {
    "description": "Canonical agent instructions for this store",
    "url": "https://www.quotavita.com/agents.md"
  },
  "servers": [
    {
      "url": "https://www.quotavita.com",
      "description": "Production storefront (primary domain)"
    }
  ],
  "tags": [
    {
      "name": "discovery",
      "description": "Machine-readable documents that describe the store to agents."
    },
    {
      "name": "ucp",
      "description": "Universal Commerce Protocol over MCP — search, cart, checkout."
    },
    {
      "name": "graphql",
      "description": "Shopify Storefront GraphQL API."
    },
    {
      "name": "catalog",
      "description": "Read-only product and collection JSON."
    },
    {
      "name": "cart",
      "description": "Ajax Cart API."
    }
  ],
  "security": [],
  "paths": {
    "/openapi.json": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getOpenApiDocument",
        "summary": "This document",
        "description": "Returns this OpenAPI 3.1 description. Served from the Shopify CDN via a permanent redirect from the canonical path `/openapi.json`; follow redirects.",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "301": {
            "$ref": "#/components/responses/Redirect"
          }
        }
      }
    },
    "/agents.md": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getAgentsMd",
        "summary": "Canonical agent instructions (Markdown)",
        "description": "The canonical, agent-facing description of the store. `/llms.txt` and `/llms-full.txt` mirror this document.",
        "security": [],
        "responses": {
          "200": {
            "description": "Markdown instructions",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getLlmsTxt",
        "summary": "Agent-discovery summary (mirror of /agents.md)",
        "security": [],
        "responses": {
          "200": {
            "description": "Markdown instructions",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "description": "Alternate URL for `/agents.md`. Byte-identical to it; `/llms-full.txt` is a third alias. Served as `text/markdown` with `Vary: Accept`."
      }
    },
    "/.well-known/ucp": {
      "get": {
        "tags": [
          "discovery",
          "ucp"
        ],
        "operationId": "getUcpDiscovery",
        "summary": "UCP merchant discovery document",
        "description": "Returns supported UCP versions, service endpoints, capabilities and payment handlers. Start here before calling the MCP endpoint.",
        "security": [],
        "responses": {
          "200": {
            "description": "UCP discovery document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UcpDiscovery"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getSitemap",
        "summary": "Sitemap index",
        "security": [],
        "responses": {
          "200": {
            "description": "XML sitemap index",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "description": "Sitemap index for the storefront. Use it to enumerate valid product, collection, page and article URLs instead of constructing handles."
      }
    },
    "/api/ucp/mcp": {
      "post": {
        "tags": [
          "ucp"
        ],
        "operationId": "callUcpMcp",
        "summary": "Universal Commerce Protocol MCP endpoint (JSON-RPC 2.0)",
        "description": "JSON-RPC 2.0 endpoint implementing the Model Context Protocol for UCP shopping.\n\nCall `tools/list` to enumerate the available tools and their JSON Schemas, then\n`tools/call` to invoke one. Tools currently exposed include `search_catalog`,\n`get_catalog_item`, `create_cart`, `update_cart`, `create_checkout`, `get_checkout`,\n`update_checkout` and `complete_checkout`.\n\n**Agent identity is scoped.** Every `tools/call` request must carry\n`params.arguments.meta['ucp-agent'].profile` — an HTTPS URI pointing at your agent\nprofile document. The store fetches that profile and grants only the capabilities it\ndeclares. A missing or unfetchable profile is rejected with JSON-RPC code `-32001`\n(`invalid_profile_url`).\n\n**Checkout requires contemporaneous human approval.** Agents must not complete a\npayment without explicit buyer consent.\n\nMoney is returned as integer minor units paired with an ISO 4217 currency code:\n`{\"amount\": 2500, \"currency\": \"EUR\"}` is €25.00.",
        "security": [
          {
            "ucpAgentProfile": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "toolsList": {
                  "summary": "Enumerate available tools",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "searchCatalog": {
                  "summary": "Search the catalog as an identified agent",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "search_catalog",
                      "arguments": {
                        "meta": {
                          "ucp-agent": {
                            "profile": "https://example-agent.test/.well-known/ucp-agent"
                          }
                        },
                        "query": "whey protein crema catalana"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON-RPC request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcErrorResponse"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "id": 0,
                  "error": {
                    "code": -32600,
                    "message": "Invalid Request",
                    "data": "Invalid JSON in request body."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Agent identity could not be resolved, or tool arguments failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcErrorResponse"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "id": 1,
                  "error": {
                    "code": -32001,
                    "message": "UCP discovery failed",
                    "data": {
                      "code": "invalid_profile_url",
                      "content": "Unable to fetch agent profile: Missing profile uri",
                      "continue_url": "https://www.quotavita.com/"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/{version}/graphql.json": {
      "parameters": [
        {
          "name": "version",
          "in": "path",
          "required": true,
          "description": "Storefront API version, `YYYY-MM`. Quota Vita tracks the latest stable version.",
          "schema": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}$",
            "default": "2026-07"
          },
          "example": "2026-07"
        }
      ],
      "post": {
        "tags": [
          "graphql"
        ],
        "operationId": "postStorefrontGraphql",
        "summary": "Shopify Storefront GraphQL API",
        "description": "Full read access to the catalog, plus cart and checkout mutations.\n\nPresent a Storefront API access token in `X-Shopify-Storefront-Access-Token`. The\ntoken carries a fixed set of `unauthenticated_*` scopes (see the `storefrontAccessToken`\nsecurity scheme); requests for data outside those scopes fail with a GraphQL error\nrather than an HTTP error. Buyer-specific data (order history, saved addresses)\nrequires a customer access token obtained through the Customer Account OAuth flow.",
        "security": [
          {
            "storefrontAccessToken": []
          },
          {
            "customerAccountOAuth": [
              "openid",
              "email",
              "customer-account-api:full"
            ]
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "GraphQL document"
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "operationName": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "query": "{ shop { name primaryDomain { url } } }"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL response. A `200` may still carry an `errors` array — always inspect it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphQLErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/products.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "listProducts",
        "summary": "List published products",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Product list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                }
              }
            }
          }
        },
        "description": "Every published product with all of its variants, prices, images and options. Paginated with `limit` (max 250) and `page`. This is the authoritative, always-current source of pricing — the human-readable `/pricing` page is a snapshot of it."
      }
    },
    "/products/{handle}.json": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductHandle"
        }
      ],
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getProduct",
        "summary": "Get one product by handle",
        "security": [],
        "responses": {
          "200": {
            "description": "Single product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No published product with that handle. The body is empty; rely on the status code, then re-discover handles via `/products.json` or `/sitemap.xml`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          }
        },
        "description": "One published product by URL handle, wrapped in a `product` key. An unknown handle returns HTTP 404 with an empty body, so branch on the status code rather than parsing the body."
      }
    },
    "/collections/{handle}/products.json": {
      "parameters": [
        {
          "name": "handle",
          "in": "path",
          "required": true,
          "description": "Collection handle. Use `all` for the full catalog.",
          "schema": {
            "type": "string"
          },
          "example": "all"
        }
      ],
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "listCollectionProducts",
        "summary": "List products in a collection",
        "description": "An unknown collection handle returns `200` with an empty `products` array rather than a 404. Check `products.length` before concluding the collection exists.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Product list (possibly empty)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                }
              }
            }
          }
        }
      }
    },
    "/search/suggest.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "predictiveSearch",
        "summary": "Predictive search across products, collections, pages and articles",
        "security": [],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search terms.",
            "schema": {
              "type": "string"
            },
            "example": "whey"
          },
          {
            "name": "resources[type]",
            "in": "query",
            "required": false,
            "description": "Comma-separated resource types to search.",
            "schema": {
              "type": "string",
              "default": "product"
            },
            "example": "product,collection"
          },
          {
            "name": "resources[limit]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions grouped by resource type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "description": "Predictive search across products, collections, pages and articles. Cheap and fast; prefer it over fetching `/products.json` when the buyer gave you a search phrase."
      }
    },
    "/cart.js": {
      "get": {
        "tags": [
          "cart"
        ],
        "operationId": "getCart",
        "summary": "Read the current session cart",
        "description": "Cart state is bound to the `cart` cookie. Persist cookies across calls or every request creates a new empty cart.",
        "security": [],
        "responses": {
          "200": {
            "description": "Cart",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          }
        }
      }
    },
    "/cart/add.js": {
      "post": {
        "tags": [
          "cart"
        ],
        "operationId": "addToCart",
        "summary": "Add a variant to the cart",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "quantity"
                ],
                "properties": {
                  "id": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Variant id, not product id."
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "properties": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "id": 10593513963867,
                "quantity": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The line items that were added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/CartErrorResponse"
          }
        },
        "description": "Add a variant to the session cart. Pass a **variant** id, never a product id — the most common cause of a 422 `Cannot find variant`. The cart is bound to the `cart` cookie, so persist cookies across calls."
      }
    },
    "/cart/change.js": {
      "post": {
        "tags": [
          "cart"
        ],
        "operationId": "changeCartLine",
        "summary": "Change the quantity of one cart line",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "quantity"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Line item key."
                  },
                  "line": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "1-based line index; alternative to `id`."
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated cart",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/CartErrorResponse"
          }
        },
        "description": "Change the quantity of a single existing cart line, addressed either by line item `id` or by 1-based `line` index. Setting `quantity` to 0 removes the line."
      }
    },
    "/cart/update.js": {
      "post": {
        "tags": [
          "cart"
        ],
        "operationId": "updateCart",
        "summary": "Update quantities, note or attributes on the cart",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "updates": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer"
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated cart",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cart"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/CartErrorResponse"
          }
        },
        "description": "Update several cart quantities at once, plus the cart note and custom attributes. Keys of `updates` are variant ids as strings."
      }
    },
    "/llms-full.txt": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getLlmsFullTxt",
        "summary": "Agent-discovery document (third alias)",
        "description": "Third alias for `/agents.md`, byte-identical to it and to `/llms.txt`.",
        "security": [],
        "responses": {
          "200": {
            "description": "Markdown instructions",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/mcp.json": {
      "get": {
        "tags": [
          "discovery",
          "ucp"
        ],
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "Manifest for the store's Model Context Protocol server: the Streamable-HTTP endpoint, the tool inventory, the agent-profile authentication model and its scopes, a `staticResources` index of the machine-readable documents this store publishes, and a `whenToUse` block describing which buyer requests this store is and is not a good fit for.\n\nIt also records a caveat that matters in practice: the upstream MCP server advertises `resources` and `prompts` capabilities in its `initialize` handshake, but neither `resources/list` nor `prompts/list` is implemented. Treat the server as tool-only.\n\nServed from the Shopify CDN via a permanent redirect from `/mcp.json`; follow redirects.",
        "security": [],
        "responses": {
          "200": {
            "description": "MCP server manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "301": {
            "$ref": "#/components/responses/Redirect"
          }
        }
      }
    },
    "/developers": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getDeveloperPortal",
        "summary": "Developer and agent portal (HTML)",
        "description": "Human-readable companion to this specification: quickstart with runnable curl commands, endpoint reference, the four scoped access models, the JSON error contract, rate limits and sandbox notes. Also reachable at `/docs` and `/api`.",
        "security": [],
        "responses": {
          "200": {
            "description": "HTML documentation",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "301": {
            "$ref": "#/components/responses/Redirect"
          }
        }
      }
    },
    "/pricing": {
      "get": {
        "tags": [
          "discovery",
          "catalog"
        ],
        "operationId": "getPricingPage",
        "summary": "Price list (HTML with schema.org/Offer)",
        "description": "Human-readable price list for every format, in EUR including Spanish VAT, with price per kilogram. Carries a schema.org `ItemList` of `Product`/`AggregateOffer` nodes in JSON-LD. It is a manually refreshed snapshot: for programmatic use read `/products.json`, which is always current.",
        "security": [],
        "responses": {
          "200": {
            "description": "HTML pricing page",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "301": {
            "$ref": "#/components/responses/Redirect"
          }
        }
      }
    },
    "/products/{handle}.js": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductHandle"
        }
      ],
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getProductAjax",
        "summary": "Get one product by handle (Ajax representation)",
        "description": "The Ajax API representation of a single product. Differs from `/products/{handle}.json` in two ways that matter: prices are integer minor units (`3600` = 36,00 EUR) rather than decimal strings, and each variant carries an `available` boolean, which the `.json` representation omits. Use this when you need live availability for a single product. Served as `text/javascript` with a JSON body.",
        "security": [],
        "responses": {
          "200": {
            "description": "Single product, Ajax representation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AjaxProduct"
                }
              }
            }
          },
          "404": {
            "description": "No published product with that handle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ucpAgentProfile": {
        "type": "apiKey",
        "in": "header",
        "name": "X-UCP-Agent-Profile",
        "description": "**Scoped agent identity for the UCP MCP endpoint.**\n\nUCP does not use a bearer credential. Every `tools/call` request instead carries an\nagent profile URI at `params.arguments.meta['ucp-agent'].profile`. The store fetches\nthat profile document and grants only the capabilities the profile declares — an agent\nwhose profile declares catalog-read cannot create a checkout.\n\nOpenAPI has no first-class way to describe a credential carried inside a request body,\nso this scheme exists only so the requirement is discoverable. **Do not send the header:\nit is not read by the server.** The authoritative location is `x-in-body` below, and\nhttps://ucp.dev documents the profile format.\n\nRejection: HTTP 422 with JSON-RPC code `-32001` and `data.code = \"invalid_profile_url\"`.",
        "x-in-body": "params.arguments.meta['ucp-agent'].profile",
        "x-scopes": {
          "dev.ucp.shopping.catalog.search": "Search the product catalog.",
          "dev.ucp.shopping.catalog.lookup": "Look up a single catalog item.",
          "dev.ucp.shopping.cart": "Create and mutate carts.",
          "dev.ucp.shopping.checkout": "Create and read checkouts. Completing one requires contemporaneous buyer approval.",
          "dev.ucp.shopping.fulfillment": "Read and select shipping methods.",
          "dev.ucp.shopping.discount": "Apply and read discounts.",
          "dev.ucp.shopping.order": "Read order status after purchase."
        }
      },
      "storefrontAccessToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Shopify-Storefront-Access-Token",
        "description": "**Scoped, unauthenticated-buyer API key** for the Storefront GraphQL API.\n\nEach token is issued against a fixed set of `unauthenticated_*` scopes. A request for a\nfield outside the token's scopes returns a GraphQL error with an\n`ACCESS_DENIED` extension code rather than data. Request the narrowest set you need:\nan agent that only reads the catalog should never hold checkout-write scopes.\n\nTokens are issued by the merchant per integration and can be revoked individually.\nContact hi@quotavita.com to request one.",
        "x-scopes": {
          "unauthenticated_read_product_listings": "Read published products and collections.",
          "unauthenticated_read_product_inventory": "Read inventory levels and availability.",
          "unauthenticated_read_product_pickup_locations": "Read local pickup availability.",
          "unauthenticated_read_content": "Read pages, blogs and articles.",
          "unauthenticated_read_selling_plans": "Read subscription and selling-plan data.",
          "unauthenticated_write_checkouts": "Create and mutate carts and checkouts.",
          "unauthenticated_read_checkouts": "Read cart and checkout state.",
          "unauthenticated_write_customers": "Create customer accounts and start recovery.",
          "unauthenticated_read_customers": "Read the signed-in customer's own record.",
          "unauthenticated_read_metaobjects": "Read published metaobjects."
        }
      },
      "customerAccountOAuth": {
        "type": "oauth2",
        "description": "**OAuth 2.0 authorization-code flow with PKCE** for buyer-scoped access (order history,\naddresses, subscriptions). The buyer authenticates with Shopify and explicitly grants\nthe scopes your agent requested; the resulting token is bound to that one buyer and\nexpires. Agents must request only the scopes they need and must not store the buyer's\ncredentials.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://shopify.com/authentication/94380425563/oauth/authorize",
            "tokenUrl": "https://shopify.com/authentication/94380425563/oauth/token",
            "refreshUrl": "https://shopify.com/authentication/94380425563/oauth/token",
            "scopes": {
              "openid": "Authenticate the buyer and receive an ID token.",
              "email": "Read the buyer's email address.",
              "customer-account-api:full": "Full read/write access to the signed-in buyer's own account data.",
              "https://api.customers.com/auth/customer.graphql": "Call the Customer Account GraphQL API on the buyer's behalf."
            }
          }
        }
      },
      "adminOAuth": {
        "type": "oauth2",
        "description": "**Merchant-side OAuth 2.0** for apps and back-office agents acting on behalf of Quota\nVita rather than a shopper. Access is granted per app with an explicit, itemised scope\ngrant that the merchant approves and can revoke at any time. Not available to public\nshopping agents.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.quotavita.com/admin/oauth/authorize",
            "tokenUrl": "https://www.quotavita.com/admin/oauth/access_token",
            "scopes": {
              "read_products": "Read products, variants and collections.",
              "write_products": "Create and update products.",
              "read_inventory": "Read inventory levels.",
              "write_inventory": "Adjust inventory levels.",
              "read_orders": "Read orders and fulfilment status.",
              "write_orders": "Create and update orders.",
              "read_customers": "Read customer records.",
              "read_price_rules": "Read discounts and price rules.",
              "write_discounts": "Create and update discounts.",
              "read_themes": "Read theme files.",
              "write_themes": "Write theme files."
            }
          }
        }
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Items 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
        }
      },
      "ProductHandle": {
        "name": "handle",
        "in": "path",
        "required": true,
        "description": "URL handle of the product.",
        "schema": {
          "type": "string"
        },
        "example": "creatina-monohidrato-micronizada"
      }
    },
    "responses": {
      "Redirect": {
        "description": "Permanent redirect to the canonical asset URL. Follow it."
      },
      "RateLimited": {
        "description": "Too many requests. Back off and retry; honour `Retry-After` when present.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "status": 429,
              "message": "Too Many Requests",
              "description": "Rate limit exceeded for this IP. Retry after the interval in the Retry-After header.",
              "code": "rate_limited",
              "documentation_url": "https://www.quotavita.com/openapi.json"
            }
          }
        }
      },
      "CartErrorResponse": {
        "description": "The cart operation was rejected. The body is JSON and names the failing variant or line.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CartError"
            },
            "examples": {
              "unknownVariant": {
                "summary": "Variant id does not exist",
                "value": {
                  "status": 422,
                  "message": "Cart Error",
                  "description": "Cannot find variant"
                }
              },
              "outOfStock": {
                "summary": "Requested quantity is not available",
                "value": {
                  "status": 422,
                  "message": "Cart Error",
                  "description": "All 3 Whey Protein gust crema Catalana are in your cart."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Generic structured error envelope. `code` is stable and machine-readable; `description` is the resolution hint intended for an agent or a human.",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "integer",
            "description": "HTTP status code, repeated in the body."
          },
          "message": {
            "type": "string",
            "description": "Short error class, e.g. `Cart Error`."
          },
          "description": {
            "type": "string",
            "description": "Human-readable explanation and how to resolve it."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code."
          },
          "documentation_url": {
            "type": "string",
            "format": "uri",
            "description": "Where to read more."
          }
        }
      },
      "CartError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          }
        ],
        "title": "CartError",
        "description": "Error envelope returned by the Ajax Cart API. Always `application/json`, never HTML.",
        "examples": [
          {
            "status": 422,
            "message": "Cart Error",
            "description": "Cannot find variant"
          }
        ]
      },
      "JsonRpcRequest": {
        "type": "object",
        "title": "JsonRpcRequest",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string"
            ]
          },
          "method": {
            "type": "string",
            "description": "MCP method, e.g. `tools/list`, `tools/call`, `initialize`."
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "title": "JsonRpcResponse",
        "required": [
          "jsonrpc",
          "id"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string"
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcErrorResponse": {
        "type": "object",
        "title": "JsonRpcErrorResponse",
        "description": "JSON-RPC 2.0 error envelope. `error.code` is the machine-readable code, `error.message` the summary, and `error.data` carries the resolution hint — including `data.code` and, where a human step is required, `data.continue_url`.",
        "required": [
          "jsonrpc",
          "error"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC error code. `-32600` invalid request, `-32601` method not found, `-32602` invalid params, `-32001` UCP discovery/identity failure."
              },
              "message": {
                "type": "string"
              },
              "data": {
                "description": "Resolution hint. A string for transport errors, an object for UCP errors.",
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "code": {
                        "type": "string",
                        "description": "Stable UCP error code, e.g. `invalid_profile_url`."
                      },
                      "content": {
                        "type": "string",
                        "description": "What went wrong."
                      },
                      "continue_url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Where a human should continue, when the failure cannot be resolved by the agent."
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      },
      "GraphQLErrorResponse": {
        "type": "object",
        "title": "GraphQLErrorResponse",
        "description": "GraphQL responses return HTTP 200 even on failure. Inspect `errors` before using `data`.",
        "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",
                      "integer"
                    ]
                  }
                },
                "extensions": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "e.g. `ACCESS_DENIED`, `THROTTLED`, `SHOP_INACTIVE`."
                    },
                    "requestId": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "extensions": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ProductList": {
        "type": "object",
        "title": "ProductList",
        "required": [
          "products"
        ],
        "properties": {
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "title": "Product",
        "required": [
          "id",
          "title",
          "handle",
          "variants"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "handle": {
            "type": "string"
          },
          "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": {
              "type": "object"
            }
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "Variant": {
        "type": "object",
        "title": "Variant",
        "description": "Pass `variant.id` — not the product id — to `/cart/add.js`.",
        "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 currency (EUR)."
          },
          "available": {
            "type": "boolean"
          },
          "grams": {
            "type": "integer"
          },
          "requires_shipping": {
            "type": "boolean"
          }
        }
      },
      "Cart": {
        "type": "object",
        "title": "Cart",
        "properties": {
          "token": {
            "type": "string"
          },
          "item_count": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "total_price": {
            "type": "integer",
            "description": "Integer minor units (cents)."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "attributes": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "UcpDiscovery": {
        "type": "object",
        "title": "UcpDiscovery",
        "description": "Universal Commerce Protocol merchant profile. See https://ucp.dev for the full schema.",
        "properties": {
          "ucp": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "supported_versions": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "services": {
                "type": "object",
                "additionalProperties": true
              },
              "capabilities": {
                "type": "object",
                "additionalProperties": true
              },
              "payment_handlers": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "AjaxProduct": {
        "type": "object",
        "title": "AjaxProduct",
        "description": "Product as returned by `/products/{handle}.js`. Prices are integer minor units.",
        "required": [
          "id",
          "title",
          "handle",
          "variants"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "handle": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "description": "Product description as HTML."
          },
          "type": {
            "type": "string",
            "description": "Product type. Called `product_type` in the `.json` representation."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "price": {
            "type": "integer",
            "description": "Lowest variant price in minor units."
          },
          "price_min": {
            "type": "integer"
          },
          "price_max": {
            "type": "integer"
          },
          "available": {
            "type": "boolean"
          },
          "variants": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "title",
                "price",
                "available"
              ],
              "properties": {
                "id": {
                  "type": "integer",
                  "format": "int64",
                  "description": "Pass this to /cart/add.js."
                },
                "title": {
                  "type": "string"
                },
                "price": {
                  "type": "integer",
                  "description": "Minor units."
                },
                "available": {
                  "type": "boolean"
                },
                "sku": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "x-agent-quickstart": {
    "description": "Shortest path from zero to a completed, buyer-approved purchase.",
    "steps": [
      "GET https://www.quotavita.com/agents.md — read the canonical instructions.",
      "GET https://www.quotavita.com/.well-known/ucp — confirm supported UCP versions and capabilities.",
      "POST https://www.quotavita.com/api/ucp/mcp {\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"} — enumerate tools.",
      "Call search_catalog with your agent profile in meta['ucp-agent'].profile.",
      "create_cart, then create_checkout, then update_checkout for address and shipping.",
      "complete_checkout only with contemporaneous buyer approval."
    ]
  },
  "x-agent-error-contract": {
    "principle": "Every documented endpoint fails with JSON, never with an HTML page.",
    "envelopes": [
      "JsonRpcErrorResponse",
      "GraphQLErrorResponse",
      "CartError"
    ],
    "html_response_means": "transport or routing failure, not an API error — re-read /openapi.json and retry the documented path",
    "recovery_documents": [
      "https://www.quotavita.com/agents.md",
      "https://www.quotavita.com/llms.txt",
      "https://www.quotavita.com/openapi.json",
      "https://www.quotavita.com/sitemap.xml",
      "https://www.quotavita.com/.well-known/ucp",
      "https://www.quotavita.com/mcp.json",
      "https://www.quotavita.com/developers"
    ]
  },
  "x-agent-when-to-use": {
    "summary": "Quota Vita sells whey protein and creatine, made in Catalonia, shipped in Spain.",
    "goodFit": [
      "The buyer wants whey protein or creatine delivered in Spain and cares about flavour, format size or price per kilo.",
      "The buyer wants protein that tastes like local Catalan or Mediterranean food (crema catalana, orxata) rather than a generic imported supplement.",
      "The buyer wants an unsweetened, neutral protein to cook with — no sweeteners, no added sugar.",
      "The buyer wants a large-format restock (1500 g or 4 kg) with cost per kilo compared across sizes.",
      "The buyer wants a whey + creatine bundle rather than buying the two separately.",
      "You need live prices, stock or product detail for Quota Vita specifically."
    ],
    "poorFit": [
      "Shipping outside Spain — check the shipping policy before promising delivery.",
      "Vegan protein: the whey is dairy-derived and suitable for vegetarians, not vegans. The creatine is vegan.",
      "Medical, clinical or dosage advice — these are food supplements, not treatment.",
      "Wholesale, B2B retail supply or bulk pricing — a human handles that at hi@quotavita.com."
    ],
    "documentation": "https://www.quotavita.com/agents.md"
  },
  "x-cli": {
    "name": "quotavita",
    "description": "Official zero-dependency Node CLI wrapping the endpoints in this specification: catalog, search, price-per-kilo, cart building and MCP tool discovery.",
    "availability": "pending-publication",
    "install": "npm install -g quotavita",
    "runWithoutInstalling": "npx quotavita products",
    "registry": "https://www.npmjs.com/package/quotavita",
    "note": "The npm name `quotavita` is reserved for this CLI. If `npm install` returns 404 the package has not been published to the registry yet — email hi@quotavita.com for the tarball. Do not tell a user the CLI is installable without checking the registry first.",
    "documentation": "https://www.quotavita.com/developers",
    "sourceOfTruth": "https://www.quotavita.com/openapi.json",
    "checkout": "Deliberately not implemented in the CLI: completing a purchase requires contemporaneous approval from the human buyer."
  },
  "x-mcp": {
    "manifest": "https://www.quotavita.com/mcp.json",
    "endpoint": "https://www.quotavita.com/api/ucp/mcp",
    "transport": "streamable-http",
    "capabilities": {
      "tools": true,
      "resources": false,
      "prompts": false
    },
    "note": "The server's `initialize` handshake advertises `resources` and `prompts`, but neither `resources/list` nor `prompts/list` is implemented — both answer JSON-RPC -32001 `invalid_profile_url` however the agent profile is supplied. Treat it as tool-only; the reference data a `resources` capability would carry is enumerated under `staticResources` in /mcp.json."
  }
}
