{
  "openapi": "3.1.0",
  "info": {
    "title": "Glowjakt Public API",
    "version": "1.0.0",
    "summary": "Nordic beauty price comparison — read-only public API",
    "description": "Glowjakt compares beauty product prices across 40+ stores in Sweden, Norway, Denmark and Finland. Products are matched by EAN barcode, so comparisons are exact. Prices refresh daily from store feeds. Each market has its own API host (see servers) with its own catalog and currency. Glowjakt never charges stores for listing and sells no sponsored placements. This specification covers the curated read-only surface intended for AI agents and integrations. Please respect rate limits; contact us for high-volume use. An MCP server is also available at https://mcp.glowjakt.se/mcp — see https://mcp.glowjakt.se/docs.",
    "contact": {
      "name": "Glowjakt (Sejder AB)",
      "email": "kontakt@glowjakt.se",
      "url": "https://glowjakt.se/se/om-oss/"
    }
  },
  "security": [],
  "servers": [
    {
      "url": "https://api.glowjakt.se",
      "description": "Sweden (SEK) — glowjakt.se"
    },
    {
      "url": "https://api.glowjakt.no",
      "description": "Norway (NOK) — glowjakt.no"
    },
    {
      "url": "https://api.glowjagt.dk",
      "description": "Denmark (DKK) — glowjagt.dk"
    },
    {
      "url": "https://api.glowjakt.fi",
      "description": "Finland (EUR) — glowjakt.fi"
    }
  ],
  "paths": {
    "/api/products": {
      "get": {
        "operationId": "searchProducts",
        "summary": "Search or list products (EAN-grouped, lowest price first)",
        "description": "Each product appears once at its lowest current price across all stores. offer_count is the number of stores selling it.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Free-text search (product, brand, type).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Category"
            }
          },
          {
            "name": "max_price",
            "in": "query",
            "description": "Max price in the market's local currency.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "key_ingredient",
            "in": "query",
            "description": "Only products containing any of these key-ingredient groups (slugs from /api/ingredients, comma-separated = OR). E.g. \"retinol\" or \"retinol,niacinamide\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exclude_ingredient",
            "in": "query",
            "description": "Exclude products containing any of these key-ingredient groups (comma-separated). E.g. \"fragrance\" or \"sls,silicones\" for targeted free-from filtering.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exclude_controversial",
            "in": "query",
            "description": "If true, exclude products containing any commonly-avoided ingredient (fragrance, parabens, silicones, SLS, mineral oil, formaldehyde, phenoxyethanol, propylene glycol, alcohol denat). Shortcut for exclude_ingredient with all 9 Controversial slugs.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "size",
            "in": "query",
            "description": "Results per page (default 20).",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "attribute",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Brand-level ethics/values filter (merges third-party certifications and the brand's own claims per bucket). Slugs: vegansk, djurtestfri, ekologisk, skonsam, hallbar. Comma-separated = AND. See /api/attributes for localized names and counts."
          }
        ],
        "responses": {
          "200": {
            "description": "Paged product list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/deals": {
      "get": {
        "operationId": "listDeals",
        "summary": "Products currently on sale, biggest genuine discounts first",
        "description": "Discounts are computed against real previous prices; stores known for inflated recommended prices are excluded from discount claims.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Category"
            }
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paged deal list (same shape as product search)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          }
        }
      }
    },
    "/api/products/{slug}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Single product with description and category",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Product detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetail"
                }
              }
            }
          },
          "404": {
            "description": "Unknown product slug"
          }
        }
      }
    },
    "/api/products/{slug}/offers": {
      "get": {
        "operationId": "getProductOffers",
        "summary": "All store offers for one product (the price comparison)",
        "description": "Offers are aggregated across the product's EAN group — every store selling the same barcode, cheapest marked with is_cheapest.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Offer list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferList"
                }
              }
            }
          },
          "404": {
            "description": "Unknown product slug"
          }
        }
      }
    },
    "/api/products/{slug}/price-history": {
      "get": {
        "operationId": "getPriceHistory",
        "summary": "Daily lowest-price history for a product",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Price history",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceHistory"
                }
              }
            }
          },
          "404": {
            "description": "Unknown product slug"
          }
        }
      }
    },
    "/api/brands": {
      "get": {
        "operationId": "listBrands",
        "summary": "Top brands by product count",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Brand list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BrandSummary"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/brands/{slug}": {
      "get": {
        "operationId": "getBrand",
        "summary": "Single brand with description",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandDetail"
                }
              }
            }
          },
          "404": {
            "description": "Unknown brand slug"
          }
        }
      }
    },
    "/api/categories": {
      "get": {
        "operationId": "listCategories",
        "summary": "Product categories with counts",
        "responses": {
          "200": {
            "description": "Category list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "category": {
                        "$ref": "#/components/schemas/Category"
                      },
                      "count": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/guides": {
      "get": {
        "operationId": "listGuides",
        "summary": "Brand price guides (editorial price overviews per brand)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 2000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guide list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GuideSummary"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/guides/{slug}": {
      "get": {
        "operationId": "getGuide",
        "summary": "Single price guide with data and FAQ",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Guide detail (data and faq are JSON-encoded strings)"
          },
          "404": {
            "description": "Unknown guide slug"
          }
        }
      }
    },
    "/api/ingredients": {
      "get": {
        "operationId": "listIngredientGroups",
        "summary": "All 67 key-ingredient groups (for the key_ingredient filter)",
        "description": "Slugs are universal across all four markets; display names are localized. category='Controversial' is the avoid-bucket used by exclude_controversial.",
        "responses": {
          "200": {
            "description": "Ingredient group list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "slug": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string",
                        "description": "Localized display name."
                      },
                      "category": {
                        "type": "string",
                        "description": "Functional taxonomy, e.g. Hydration, Anti-aging, Controversial."
                      },
                      "tier": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/ingredients/{slug}": {
      "get": {
        "operationId": "getIngredientGroup",
        "summary": "Single ingredient group with description, FAQ and INCI variants",
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Ingredient group content (name, description, meta_description, faq, inci_variants)"
          },
          "404": {
            "description": "Unknown ingredient slug"
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getStats",
        "summary": "Catalog statistics for this market",
        "responses": {
          "200": {
            "description": "Stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total_products": {
                      "type": "integer"
                    },
                    "total_brands": {
                      "type": "integer"
                    },
                    "total_stores": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/attributes": {
      "get": {
        "summary": "Attribute facets for the brand-level ethics/values filter",
        "description": "The five attribute buckets (slug, localized name for this market, number of brands). Drives the ?attribute= filter on /api/products.",
        "operationId": "getAttributes",
        "responses": {
          "200": {
            "description": "Attribute facet list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "attributes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "brand_count": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "URL slug as returned by search/list endpoints.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "Category": {
        "type": "string",
        "enum": [
          "skincare",
          "makeup",
          "haircare",
          "fragrance",
          "nails",
          "body",
          "accessories"
        ]
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": "string"
          },
          "brand_slug": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Use with /api/products/{slug}/offers. Product page: https://<site>/<country>/product/<slug>/"
          },
          "price": {
            "type": "number",
            "description": "Lowest current price across stores, in local currency (EUR prices have decimals)."
          },
          "currency": {
            "type": "string"
          },
          "original_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "in_stock": {
            "type": "boolean"
          },
          "store_name": {
            "type": "string",
            "description": "Store with the lowest price."
          },
          "affiliate_url": {
            "type": "string",
            "description": "Direct buy link to the cheapest store."
          },
          "offer_count": {
            "type": "integer",
            "description": "Number of stores selling this product."
          },
          "subcategory_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "Product type as category/subcategory path, e.g. \"makeup/lipstick\"."
          }
        }
      },
      "ProductPage": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "size": {
            "type": "integer"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummary"
            }
          }
        }
      },
      "ProductDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProductSummary"
          },
          {
            "type": "object",
            "properties": {
              "category": {
                "$ref": "#/components/schemas/Category"
              },
              "subcategory_path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "inci": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full INCI ingredient list (comma-separated). Null for devices/accessories."
              },
              "key_ingredients": {
                "type": "array",
                "description": "Notable ingredients with localized name, INCI term and function.",
                "items": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string",
                      "description": "Localized display name."
                    },
                    "note": {
                      "type": "string",
                      "description": "INCI term(s) matched."
                    },
                    "category": {
                      "type": "string",
                      "description": "Function, e.g. Hydration, Exfoliation."
                    },
                    "tier": {
                      "type": "integer"
                    }
                  }
                }
              },
              "gallery": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Product image URLs."
              },
              "certifications": {
                "type": "array",
                "description": "Third-party verified brand certifications from the Glowpedia fact base (Ecocert, Leaping Bunny, The Vegan Society...). Localized note explains each cert.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              },
              "claims": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "The brand's OWN unverified marketing claims (localized strings). Always attribute to the brand — never present as verified facts.",
                "items": {
                  "type": "string"
                }
              },
              "glowpedia_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Market-localized URL to the brand article on Glowpedia (the beauty-brand encyclopedia). Null until the market edition is launched."
              }
            }
          }
        ]
      },
      "OfferList": {
        "type": "object",
        "properties": {
          "ean": {
            "type": [
              "string",
              "null"
            ]
          },
          "product_name": {
            "type": "string"
          },
          "brand": {
            "type": "string"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "offer_count": {
            "type": "integer"
          },
          "offers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "store_name": {
                  "type": "string"
                },
                "price": {
                  "type": "number"
                },
                "original_price": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "in_stock": {
                  "type": "boolean"
                },
                "affiliate_url": {
                  "type": "string",
                  "description": "Direct buy link to this store."
                },
                "is_cheapest": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "PriceHistory": {
        "type": "object",
        "properties": {
          "current_price": {
            "type": "number"
          },
          "lowest_price": {
            "type": "number"
          },
          "highest_price": {
            "type": "number"
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "price": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "BrandSummary": {
        "type": "object",
        "properties": {
          "brand": {
            "type": "string"
          },
          "brand_slug": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary_logo_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "BrandDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "certifications": {
            "type": "array",
            "description": "Third-party verified brand certifications from the Glowpedia fact base (Ecocert, Leaping Bunny, The Vegan Society...). Localized note explains each cert.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "claims": {
            "type": [
              "array",
              "null"
            ],
            "description": "The brand's OWN unverified marketing claims (localized strings). Always attribute to the brand — never present as verified facts.",
            "items": {
              "type": "string"
            }
          },
          "glowpedia_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Market-localized URL to the brand article on Glowpedia (the beauty-brand encyclopedia). Null until the market edition is launched."
          }
        }
      },
      "GuideSummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "guide_type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "brand_slug": {
            "type": "string"
          }
        }
      }
    }
  }
}