{
    "openapi": "3.1.0",
    "info": {
        "title": "Sure Petcare agent product API",
        "version": "v1",
        "summary": "Read-only Sure Petcare product data, prices, availability and compatibility.",
        "description": "Beta proof of concept. Product data is strictly read-only: no endpoint here changes a product, a price, a basket or an order, and none is planned. The single exception is send_feedback, which writes only your own answer about this API and is optional. It serves a small fixed subset of the Sure Petcare range, so it is NOT a complete catalogue: absence from these results does not mean a product does not exist. Fall back to surepetcare.com before telling anyone Sure Petcare does not sell something. Field names and response shapes may still change while this is in beta.\n\nGenerated from the manifest at /api/agent/v1, which is the primary description: it also carries the conventions the responses follow. Limits: 60 requests per minute for reads, 20 feedback submissions per hour. HTTP 429 with error.code \"rate_limited\" and error.retry_after_seconds. The Retry-After and X-RateLimit-* headers carry the same information.",
        "x-status": "beta",
        "x-stage": "proof_of_concept"
    },
    "externalDocs": {
        "description": "llms.txt: the API described in prose",
        "url": "/llms.txt"
    },
    "servers": [
        {
            "url": "/api/agent/v1"
        }
    ],
    "security": [],
    "paths": {
        "/": {
            "get": {
                "operationId": "manifest",
                "summary": "Capability manifest",
                "description": "Start here. The same capabilities as this document, plus the conventions (prices, availability, URLs, unknown answers) that the responses follow.",
                "parameters": [
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                }
            }
        },
        "/overview": {
            "get": {
                "operationId": "overview",
                "summary": "The API as one HTML page of links",
                "description": "Every product record and every pairing answer as a link, for a client that will only open a URL it has seen on a page.",
                "parameters": [
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "HTML page.",
                        "content": {
                            "text/html": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                }
            }
        },
        "/products": {
            "get": {
                "operationId": "list_products",
                "summary": "List products",
                "description": "Everything in scope, as summaries: id, slug, name, price, availability and relationship counts. Enough to choose a product without fetching all of them. Paging is reported in \"paging\".",
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Page of results, 1-based.",
                        "schema": {
                            "type": "integer",
                            "default": 1,
                            "minimum": 1,
                            "description": "Page of results, 1-based."
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Values above the maximum are clamped to it, not rejected.",
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 50,
                            "description": "Values above the maximum are clamped to it, not rejected."
                        }
                    },
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                },
                "x-example-request": "https://www.surepetcare.com/api/agent/v1/products?per_page=5"
            }
        },
        "/search": {
            "get": {
                "operationId": "search_products",
                "summary": "Search products",
                "description": "Find products by name, SKU, category or specification value. Results carry relevance, matched_on, matched_terms and match_quality.",
                "parameters": [
                    {
                        "name": "q",
                        "in": "query",
                        "required": true,
                        "description": "Free text. Matched against name, SKU, tagline, category and specification values.",
                        "schema": {
                            "type": "string",
                            "minLength": 2,
                            "description": "Free text. Matched against name, SKU, tagline, category and specification values."
                        }
                    },
                    {
                        "name": "include_weak",
                        "in": "query",
                        "required": false,
                        "description": "Include results whose only match was a word in the specification table. Off by default when better results exist; the number withheld is reported as weak_omitted.",
                        "schema": {
                            "type": "boolean",
                            "default": false,
                            "description": "Include results whose only match was a word in the specification table. Off by default when better results exist; the number withheld is reported as weak_omitted."
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Values above the maximum are clamped to it, not rejected.",
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 50,
                            "description": "Values above the maximum are clamped to it, not rejected."
                        }
                    },
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                },
                "x-example-request": "https://www.surepetcare.com/api/agent/v1/search?q=microchip+cat+flap"
            }
        },
        "/products/{id_or_slug}": {
            "get": {
                "operationId": "get_product",
                "summary": "Get product",
                "description": "Full record: price, availability, specifications, device limits, accessories, and what the product requires to work.",
                "parameters": [
                    {
                        "name": "id_or_slug",
                        "in": "path",
                        "required": true,
                        "description": "Numeric product id, or the slug from the product record (the last segment of canonical_url). Both resolve to the same record.",
                        "schema": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9_-]+$",
                            "description": "Numeric product id, or the slug from the product record (the last segment of canonical_url). Both resolve to the same record."
                        }
                    },
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                },
                "x-example-request": "https://www.surepetcare.com/api/agent/v1/products/microchip-cat-flap"
            }
        },
        "/compatibility": {
            "get": {
                "operationId": "check_compatibility",
                "summary": "Check compatibility",
                "description": "Whether two products work together. Returns compatible, incompatible, requires, requires_intermediary or unknown, with a reason and the source of the answer.",
                "parameters": [
                    {
                        "name": "product",
                        "in": "query",
                        "required": true,
                        "description": "One of the pair. A numeric product id or a slug.",
                        "schema": {
                            "type": "string",
                            "description": "One of the pair. A numeric product id or a slug."
                        }
                    },
                    {
                        "name": "with",
                        "in": "query",
                        "required": true,
                        "description": "The other of the pair. Order does not matter. A numeric product id or a slug.",
                        "schema": {
                            "type": "string",
                            "description": "The other of the pair. Order does not matter. A numeric product id or a slug."
                        }
                    },
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    }
                },
                "x-example-request": "https://www.surepetcare.com/api/agent/v1/compatibility?product=microchip-cat-flap&with=microchip-pet-door"
            }
        },
        "/feedback": {
            "post": {
                "operationId": "send_feedback",
                "summary": "Send feedback",
                "description": "Tell us whether this API was useful and what you would want from it next. Optional, unauthenticated, and never a condition of using anything else here — see feedback_request on every response. Rate limited separately and more tightly than the read endpoints, because it is the only capability that writes. Do not include personal data.",
                "parameters": [
                    {
                        "name": "store",
                        "in": "query",
                        "required": false,
                        "description": "Store code. Decides currency, tax treatment, price, availability and translated text.",
                        "schema": {
                            "type": "string",
                            "default": "en-gb",
                            "enum": [
                                "en-gb",
                                "fr-fr",
                                "de-de"
                            ],
                            "description": "Store code. Decides currency, tax treatment, price, availability and translated text."
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/Ok"
                    },
                    "400": {
                        "$ref": "#/components/responses/Error"
                    },
                    "404": {
                        "$ref": "#/components/responses/Error"
                    },
                    "429": {
                        "$ref": "#/components/responses/Error"
                    },
                    "413": {
                        "$ref": "#/components/responses/Error"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "useful"
                                ],
                                "properties": {
                                    "useful": {
                                        "type": "string",
                                        "enum": [
                                            "yes",
                                            "partly",
                                            "no"
                                        ],
                                        "description": "Was this API useful for what you were doing?"
                                    },
                                    "wanted": {
                                        "type": "string",
                                        "maxLength": 600,
                                        "description": "What was missing, wrong, or would have helped. This is the field we are actually asking for."
                                    },
                                    "agent": {
                                        "type": "string",
                                        "maxLength": 120,
                                        "description": "What to call you. Not verified and not used to authorise anything."
                                    },
                                    "request_id": {
                                        "type": "string",
                                        "description": "The X-Request-ID of a call this is about, so what you say can be lined up with what you were doing."
                                    }
                                },
                                "example": {
                                    "useful": "partly",
                                    "wanted": "What was missing, wrong, or would have helped."
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Envelope": {
                "type": "object",
                "description": "Every response. data carries the answer; the rest says which store and deployment answered and when.",
                "required": [
                    "api_version",
                    "status",
                    "generated_at"
                ],
                "properties": {
                    "api_version": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string"
                    },
                    "store": {
                        "type": "object"
                    },
                    "environment": {
                        "type": "object"
                    },
                    "generated_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "feedback_request": {
                        "type": "object"
                    },
                    "scope": {
                        "type": "object"
                    },
                    "paging": {
                        "type": "object"
                    },
                    "data": {
                        "type": [
                            "object",
                            "array"
                        ]
                    }
                }
            },
            "ErrorEnvelope": {
                "type": "object",
                "required": [
                    "api_version",
                    "error"
                ],
                "properties": {
                    "api_version": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string"
                    },
                    "environment": {
                        "type": "object"
                    },
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "invalid_parameter",
                                    "missing_parameter",
                                    "unknown_store",
                                    "unknown_product",
                                    "out_of_scope",
                                    "not_available_in_store",
                                    "unknown_endpoint",
                                    "request_too_large",
                                    "method_not_allowed",
                                    "rate_limited",
                                    "too_many_errors"
                                ]
                            },
                            "message": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "responses": {
            "Ok": {
                "description": "The answer, in the envelope.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Envelope"
                        }
                    }
                }
            },
            "Error": {
                "description": "error.code says which, and what to do:\n\n- `invalid_parameter`: 400. A parameter was present but unusable.\n- `missing_parameter`: 400. A required parameter was absent.\n- `unknown_store`: 400. The store is not one this POC serves.\n- `unknown_product`: 404. No such product on this website.\n- `out_of_scope`: 404. The product exists, but this POC does not serve it. The website has it; the in-scope list is returned with the error.\n- `not_available_in_store`: 404. The product exists and is in scope, but is not published in the requested store.\n- `unknown_endpoint`: 404. No such endpoint. The capability list above is complete.\n- `request_too_large`: 413. send_feedback only. The body exceeded the limit and nothing was recorded.\n- `method_not_allowed`: 405. Wrong verb for that path. Every capability that reads is a GET; send_feedback is the only POST.\n- `rate_limited`: 429. See limits below.\n- `too_many_errors`: 429. Too many of your recent requests could not be answered, so further ones are declined for a short while. Unlike rate_limited this is not about your rate: successful requests are not counted and are not affected, and retrying the same failing request will not help. It means addresses or product references are being guessed. The capability list above is complete, and every capability carries an example_request that can be fetched exactly as written.\n- `no_json_body_403`: 403 with an empty body did NOT come from this API. A security layer sits in front of it, rejects some requests before they arrive, and answers them itself — so no error envelope is produced and we have no record of your request at all. Reachable on any endpoint and any method. There are two causes and you cannot tell them apart from the response, so do this in order. FIRST, assume the content of the request looked like an attack: HTML or script-like markup in a POST body, or a query string, path or header resembling SQL or a file path. Resend as plain prose — describe markup rather than pasting it, keep the body under 4 KB. This is much the more likely of the two, and retrying identically will fail identically. SECOND, only if that plain retry is also refused, treat it as your address or request rate being declined — back off and retry later rather than rephrasing further. Do not conclude on one 403 that you are blocked: one prose retry costs you a request and would clear the common case. THIRD, if backing off does not clear it either, stop. One of the possible causes is a manual blocklist that has no expiry and is lifted only by a person here, so further retries cannot succeed however long you wait. Report it to whoever you are acting for, saying this API is unreachable from your address and needs a human at Sure Petcare to clear it — the website's contact route reaches us. That is a better outcome for both of us than retrying indefinitely.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorEnvelope"
                        }
                    }
                }
            }
        }
    }
}