{
  "openapi": "3.1.0",
  "info": {
    "title": "Perkusai Shopping Search API",
    "version": "1.0.0",
    "description": "AI shopping search for the Lithuanian/EU market. Describe what you want in natural language and get real, in-stock products with prices, ratings and affiliate-tracked buy links. Full integration documentation: https://perkusai.lt/for-ai.html (en: /for-ai.en.html, ru: /for-ai.ru.html). Summary for LLM clients: https://perkusai.lt/llms.txt",
    "contact": { "name": "Perkusai", "url": "https://perkusai.lt/for-ai.html", "email": "support@perkusai.lt" }
  },
  "servers": [ { "url": "https://perkusai.lt" } ],
  "paths": {
    "/api/ai/search": {
      "get": {
        "operationId": "aiSearch",
        "summary": "Search products (compact, agent-friendly response)",
        "description": "Returns a summary, a decision guide, product items with affiliate buy_url, and a paste-ready markdown block. Present buy_url as the purchase link (affiliate-tracked).",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The shopper's request in natural language, e.g. 'nesiojamas kompiuteris darbui iki 700'." },
          { "name": "k", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 12, "default": 5 }, "description": "How many products to return." },
          { "name": "lang", "in": "query", "required": false, "schema": { "type": "string", "enum": ["lt", "en", "ru"] }, "description": "Response/query language hint." }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AiSearchResult" } } }
          },
          "400": { "description": "Empty query" },
          "429": { "description": "Rate limit exceeded" }
        }
      }
    },
    "/api/ai/stats": {
      "get": {
        "operationId": "aiStats",
        "summary": "Service status (catalogue, coverage, limits, measured response times)",
        "description": "Machine-readable status document. Response times are median/p95 over the last 7 days of production traffic, recomputed once a day — call this at most once per session, not per search.",
        "responses": {
          "200": {
            "description": "Service status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServiceStatus" } } }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "search",
        "summary": "Search products (full raw response)",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "k", "in": "query", "required": false, "schema": { "type": "integer", "default": 5 } },
          { "name": "lang", "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Full structured search response" } }
      }
    }
  },
  "components": {
    "schemas": {
      "AiSearchResult": {
        "type": "object",
        "properties": {
          "query": { "type": "string" },
          "source": { "type": "string" },
          "summary": { "type": "string" },
          "guide": { "type": ["string", "null"], "description": "A one-sentence decision tip." },
          "needs_confirmation": { "type": "boolean", "description": "True when an exact part/fitment match is not guaranteed." },
          "compat": { "type": ["string", "null"], "description": "The device/vehicle a part must fit, if any." },
          "no_match": { "type": "boolean" },
          "supported_categories": { "type": ["array", "null"], "items": { "type": "string" }, "description": "Categories actually carried; returned alongside no_match." },
          "count": { "type": "integer" },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } },
          "bought_together": {
            "type": "array",
            "description": "Optional accessories for the top pick.",
            "items": {
              "type": "object",
              "properties": {
                "title": { "type": "string" },
                "role": { "type": ["string", "null"], "description": "What the accessory is for." },
                "price": { "type": ["number", "null"] },
                "currency": { "type": "string", "example": "EUR" },
                "buy_url": { "type": "string" },
                "image_url": { "type": ["string", "null"] }
              }
            }
          },
          "coverage": {
            "type": "object",
            "description": "Which markets the results are available in, and delivery info.",
            "properties": {
              "markets": { "type": "array", "items": { "type": "string" }, "example": ["LT"] },
              "note": { "type": "string" },
              "delivery": { "type": "string" }
            }
          },
          "markdown": { "type": "string", "description": "Paste-ready markdown with clickable affiliate buy links; it also carries the affiliate disclosure." }
        }
      },
      "ServiceStatus": {
        "type": "object",
        "properties": {
          "service": { "type": "string", "example": "perkusai" },
          "status": { "type": "string", "example": "ok" },
          "catalog": {
            "type": "object",
            "properties": {
              "products": { "type": ["integer", "null"] },
              "markets": { "type": "array", "items": { "type": "string" }, "example": ["LT"] },
              "currency": { "type": "string", "example": "EUR" }
            }
          },
          "languages": { "type": "array", "items": { "type": "string" }, "example": ["lt", "en", "ru"] },
          "performance": {
            "type": ["object", "null"],
            "description": "Measured server-side, end to end. Null when the sample is too small.",
            "properties": {
              "scope": { "type": "string", "enum": ["agent", "all"], "description": "Which traffic the figures describe." },
              "window_days": { "type": "integer", "example": 7 },
              "samples": { "type": "integer" },
              "median_ms": { "type": "integer" },
              "p95_ms": { "type": "integer" },
              "avg_ms": { "type": "integer" }
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "rate_limit_per_min": { "type": "integer", "example": 40 },
              "max_k": { "type": "integer", "example": 12 },
              "cache_ttl_s": { "type": "integer", "example": 300 }
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "brand": { "type": ["string", "null"] },
          "price": { "type": ["number", "null"] },
          "currency": { "type": "string", "example": "EUR" },
          "original_price": { "type": ["number", "null"] },
          "discount_pct": { "type": ["integer", "null"] },
          "rating": { "type": ["number", "null"] },
          "stock_label": { "type": ["string", "null"] },
          "market": { "type": "string", "example": "LT", "description": "Market the product is available in." },
          "reason": { "type": ["string", "null"] },
          "tag": { "type": ["string", "null"] },
          "buy_url": { "type": "string", "description": "Affiliate-tracked purchase link. Present this as the buy link." },
          "image_url": { "type": ["string", "null"] }
        }
      }
    }
  }
}
