{
  "openapi": "3.1.0",
  "info": {
    "title": "Cosmetic Rules API",
    "version": "0.1.0",
    "summary": "AI-built, source-checked reference data for food, US tariffs and cosmetic ingredients.",
    "description": "Every response carries the dataset version and provenance. Values are copied by code from primary sources; where an AI judged something, the method and confidence are recorded.\n\nAuthentication: send your key in the `X-API-Key` header. Quotas are monthly units; most lookups cost 1 unit.\n\nPlans and prices: see the pricing page.",
    "license": {
      "name": "Proprietary; data licences per source in meta.sources"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://api.cosmeticrules.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Formula Check API",
      "description": "The formula check as data, for formulation, PLM and compliance software.",
      "x-product": "cosmetics",
      "x-product-line": "formula-check-api"
    },
    {
      "name": "Ingredient Rules API",
      "description": "Look up an ingredient by INCI, CAS or EC and get every rule in every market, with the legal quote.",
      "x-product": "cosmetics",
      "x-product-line": "ingredient-rules-api"
    },
    {
      "name": "Label Tools API",
      "description": "Fragrance allergen labelling and INCI label validation for the EU, GB and the US.",
      "x-product": "cosmetics",
      "x-product-line": "label-tools-api"
    },
    {
      "name": "Rule Change Feed",
      "description": "Dated rule events: entries into force, placing-on-market dates and sell-through deadlines, including adopted future dates.",
      "x-product": "cosmetics",
      "x-product-line": "change-feed"
    },
    {
      "name": "Account",
      "description": "Signup, account, keys, datasets and usage"
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol server for AI agents"
    },
    {
      "name": "Demo",
      "description": "Public, rate-limited demo of the sample requests shown on the sites, and the free duty calculator (no key, not metered)"
    }
  ],
  "paths": {
    "/v1/cosmetics/ingredients": {
      "get": {
        "operationId": "cosmetics_ingredients",
        "tags": [
          "Ingredient Rules API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "ingredient-rules-api",
        "summary": "Ingredient lookup by INCI, CAS or text",
        "description": "Substances (by legal-glossary or CosIng INCI name, CAS number or chemical name) with every rule that lists them, by market: EU, GB, US federal and 8 US states, with citations to the official text.\n\nQuota cost: 1 unit. MCP tool: `cosmetics_ingredient_lookup`.",
        "x-units": 1,
        "x-mcp-tool": "cosmetics_ingredient_lookup",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Salicylic Acid"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/ingredients/{id}": {
      "get": {
        "operationId": "cosmetics_ingredient",
        "tags": [
          "Ingredient Rules API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "ingredient-rules-api",
        "summary": "Get a substance by id",
        "description": "One substance (cas:<CAS>, ec:<EC>, inci:<INCI>, name:<name>) with its rules in every market and linked identifiers.\n\nQuota cost: 1 unit. MCP tool: `cosmetics_ingredient_get`.",
        "x-units": 1,
        "x-mcp-tool": "cosmetics_ingredient_get",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(cas|ec|inci|name|class):[^/]{1,190}$"
            },
            "example": "cas:69-72-7"
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/rules": {
      "get": {
        "operationId": "cosmetics_rules",
        "tags": [
          "Ingredient Rules API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "ingredient-rules-api",
        "summary": "Rules by market",
        "description": "Machine-readable rules of a market: status (prohibited, restricted, allowed colorant/preservative/UV filter, labelling threshold, state ban), each clause with its product-type scope, limits (numbers copied by code from the legal text), conditions and warnings, dates (placing on the market, sell-through), citations with verbatim quotes, and how disagreements between the two extraction passes were resolved.\n\nQuota cost: 1 unit. MCP tool: `cosmetics_rules_by_market`.",
        "x-units": 1,
        "x-mcp-tool": "cosmetics_rules_by_market",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": true,
            "description": "EU, GB, US (federal) or a US state (state rules plus US federal)",
            "schema": {
              "type": "string",
              "enum": [
                "EU",
                "GB",
                "US",
                "US-CA",
                "US-CO",
                "US-MD",
                "US-ME",
                "US-MN",
                "US-OR",
                "US-VT",
                "US-WA"
              ]
            },
            "example": "EU"
          },
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Phenoxyethanol"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "prohibited",
                "restricted",
                "labelling_threshold",
                "allowed_colorant",
                "allowed_preservative",
                "allowed_uv_filter",
                "state_ban"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/compare": {
      "get": {
        "operationId": "cosmetics_compare",
        "tags": [
          "Ingredient Rules API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "ingredient-rules-api",
        "summary": "Compare an ingredient's rules across markets",
        "description": "Every rule that lists an ingredient, market by market (a US state also shows US federal rules), with status, dates, clause limits copied by code and the entry citation; plus EU/GB divergences entry by entry.\n\nQuota cost: 1 unit. MCP tool: `cosmetics_compare_markets`.",
        "x-units": 1,
        "x-mcp-tool": "cosmetics_compare_markets",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Salicylic Acid"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "markets",
            "in": "query",
            "required": false,
            "description": "Comma-separated markets (default: all), e.g. EU,GB,US-CA",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "example": "EU,GB,US-CA"
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/categories": {
      "get": {
        "operationId": "cosmetics_categories",
        "tags": [
          "Ingredient Rules API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "ingredient-rules-api",
        "summary": "Product categories",
        "description": "The product categories the checks accept, each with its product tags.\n\nQuota cost: free. MCP tool: `cosmetics_categories`.",
        "x-units": 0,
        "x-mcp-tool": "cosmetics_categories",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/formula-check": {
      "post": {
        "operationId": "cosmetics_formula_check",
        "tags": [
          "Formula Check API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "formula-check-api",
        "summary": "Check a formula against market rules",
        "description": "For each market: PASS, FAIL, NEEDS_ATTENTION or NOT_MODELLED per ingredient and overall. Bans, maximum concentrations by product type (including on-head limits after mixing for oxidative hair dyes, group limits and limits 'calculated as'), positive lists for preservatives, UV filters and colorants, dates (placing on the market or making available), US state class bans and contaminant limits. Unmodelled wording never gives PASS, and an ingredient that resolves to no known substance (no rule, CosIng inventory INCI name, CAS or EC number) is NOT_MODELLED; a missing concentration is never treated as zero.\n\nQuota cost: 10 units. MCP tool: `cosmetics_formula_check`.",
        "x-units": 10,
        "x-mcp-tool": "cosmetics_formula_check",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_formula_check_body"
              },
              "example": {
                "markets": [
                  "EU",
                  "US-CA"
                ],
                "product_category": "body_lotion",
                "as_of": "2026-10-10",
                "ingredients": [
                  {
                    "inci": "Aqua",
                    "pct": 85
                  },
                  {
                    "inci": "Glycerin",
                    "pct": 5
                  },
                  {
                    "inci": "Phenoxyethanol",
                    "pct": 0.9,
                    "function": "preservative"
                  },
                  {
                    "inci": "Salicylic Acid",
                    "pct": 0.5,
                    "function": "preservative"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body over 512 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Body is not application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/formula-check/batch": {
      "post": {
        "operationId": "cosmetics_formula_check_batch",
        "tags": [
          "Formula Check API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "formula-check-api",
        "summary": "Check many formulas",
        "description": "Up to 50 formulas in one call, each as POST /v1/cosmetics/formula-check takes it (plus an optional id), with at most 1,000 ingredient rows and 5,000 ingredient-market checks (ingredients x markets) in all; each answers with its verdicts or its own error, and the summary counts verdicts.\n\nQuota cost: 10 units per formula. MCP tool: `cosmetics_formula_check_batch`.",
        "x-units": "10 units per formula",
        "x-mcp-tool": "cosmetics_formula_check_batch",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_formula_check_batch_body"
              },
              "example": {
                "formulas": [
                  {
                    "id": "lotion",
                    "markets": [
                      "EU",
                      "GB"
                    ],
                    "product_category": "body_lotion",
                    "as_of": "2026-10-10",
                    "ingredients": [
                      {
                        "inci": "Aqua",
                        "pct": 85
                      },
                      {
                        "inci": "Glycerin",
                        "pct": 5
                      },
                      {
                        "inci": "Phenoxyethanol",
                        "pct": 0.9,
                        "function": "preservative"
                      }
                    ]
                  },
                  {
                    "id": "shampoo",
                    "markets": [
                      "EU",
                      "GB"
                    ],
                    "product_category": "shampoo",
                    "as_of": "2026-10-10",
                    "ingredients": [
                      {
                        "inci": "Aqua",
                        "pct": 80
                      },
                      {
                        "inci": "Sodium Laureth Sulfate",
                        "pct": 12
                      },
                      {
                        "inci": "Salicylic Acid",
                        "pct": 3,
                        "function": "anti_dandruff"
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body over 512 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Body is not application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/allergen-label": {
      "post": {
        "operationId": "cosmetics_allergen_label",
        "tags": [
          "Label Tools API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "label-tools-api",
        "summary": "Allergen label calculation",
        "description": "Which fragrance allergens must be named in the ingredient list (EU and GB): concentrations from a fragrance composition and dose or given directly, isomers and group members summed (e.g. geranial + neral = Citral), leave-on and rinse-off thresholds, dated by placing on the market (the 2023 EU allergen list applies from its dates).\n\nQuota cost: 10 units. MCP tool: `cosmetics_allergen_label`.",
        "x-units": 10,
        "x-mcp-tool": "cosmetics_allergen_label",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_allergen_label_body"
              },
              "example": {
                "markets": [
                  "EU",
                  "GB"
                ],
                "product_category": "body_lotion",
                "as_of": "2026-10-10",
                "fragrance": {
                  "dose_pct": 0.8,
                  "composition": [
                    {
                      "inci": "Linalool",
                      "pct_in_fragrance": 8
                    },
                    {
                      "inci": "Limonene",
                      "pct_in_fragrance": 5
                    },
                    {
                      "inci": "Geranial",
                      "pct_in_fragrance": 0.15
                    },
                    {
                      "inci": "Neral",
                      "pct_in_fragrance": 0.1
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body over 512 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Body is not application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/label-check": {
      "post": {
        "operationId": "cosmetics_label_check",
        "tags": [
          "Label Tools API"
        ],
        "x-product": "cosmetics",
        "x-product-line": "label-tools-api",
        "summary": "Validate an INCI ingredient list",
        "description": "Checks an ingredient list as printed (EU, GB or US): descending order above 1 % (with concentrations), fragrance term, nano and CI formats, prohibited ingredients for the product type, allergens that must be named, 'may contain' use; each finding cites Article 19 of Regulation 1223/2009 (or the GB text) or 21 CFR 701.3.\n\nQuota cost: 10 units. MCP tool: `cosmetics_label_check`.",
        "x-units": 10,
        "x-mcp-tool": "cosmetics_label_check",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_label_check_body"
              },
              "example": {
                "market": "EU",
                "product_category": "body_lotion",
                "label": "Ingredients: Aqua, Glycerin, Fragrance, Phenoxyethanol, Titanium Dioxide (nano)",
                "formula": [
                  {
                    "inci": "Aqua",
                    "pct": 88
                  },
                  {
                    "inci": "Glycerin",
                    "pct": 9
                  },
                  {
                    "inci": "Fragrance",
                    "pct": 1
                  },
                  {
                    "inci": "Phenoxyethanol",
                    "pct": 0.9
                  },
                  {
                    "inci": "Titanium Dioxide",
                    "pct": 1.1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body over 512 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Body is not application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cosmetics/changes": {
      "get": {
        "operationId": "cosmetics_changes",
        "tags": [
          "Rule Change Feed"
        ],
        "x-product": "cosmetics",
        "x-product-line": "change-feed",
        "summary": "Rule change feed",
        "description": "Dated events of the rules, newest first: entries into force, placing-on-the-market dates, last days for non-compliant stock, adopted rules not yet in force, renumbering and replaced texts. Filter by date range, market and event.\n\nQuota cost: 1 unit. MCP tool: `cosmetics_changes`.",
        "x-units": 1,
        "x-mcp-tool": "cosmetics_changes",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": true,
            "description": "YYYY-MM-DD",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-01-01"
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "EU, GB, US (federal) or a US state (state rules plus US federal)",
            "schema": {
              "type": "string",
              "enum": [
                "EU",
                "GB",
                "US",
                "US-CA",
                "US-CO",
                "US-MD",
                "US-ME",
                "US-MN",
                "US-OR",
                "US-VT",
                "US-WA"
              ]
            },
            "example": "EU"
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "in_force",
                "placing_on_market_ban_or_limit_applies",
                "last_day_non_compliant_stock_may_be_made_available",
                "pending_not_yet_in_force",
                "requirement_applies_from",
                "renumbered",
                "text_replaced"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "example": 20
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units included per month in your plan on this product. Paid plans keep working above it, up to meta.usage.hard_cap_units; free plans stop at it",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/datasets": {
      "get": {
        "operationId": "datasets_list",
        "tags": [
          "Account"
        ],
        "summary": "Datasets and versions",
        "security": [],
        "description": "Datasets served, their versions, record counts, sources and licences, and load report.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "tags": [
          "Account"
        ],
        "summary": "Service health",
        "security": [],
        "description": "Status, the tariff engine's coverage, and the loaded dataset versions.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/datasets/{name}/snapshot": {
      "get": {
        "operationId": "datasets_snapshot",
        "tags": [
          "Account"
        ],
        "summary": "Bulk snapshot (Enterprise)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "gzip of the dataset's data.sqlite for the loaded version. Enterprise plan only. Free (0 units).",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "cosmetics"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "gzip file",
            "content": {
              "application/gzip": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/gzip"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not include snapshots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown dataset or no snapshot stored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup": {
      "post": {
        "operationId": "signup",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Ask for a free API key (sends a verification email)",
        "security": [],
        "description": "Deprecated: use the dashboard on the product's website (sign in with your email). Kept working until it is retired. Step 1 of 2. Checks the Cloudflare Turnstile token, refuses disposable email domains and applies per-IP and per-email limits, then emails a single-use link valid 30 minutes. No key is issued yet. The 202 answer is the same whether or not the address already has an account. Off unless SIGNUP_ENABLED.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "accept_terms",
                  "turnstile_token"
                ],
                "additionalProperties": false,
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "accept_terms": {
                    "type": "boolean",
                    "const": true
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "The token from the Turnstile widget on the signup page"
                  },
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ],
                    "description": "The product site the signup came from (the verification link points back to it)"
                  },
                  "attribution": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "Captured by the site when the visitor landed: UTM tags, document.referrer and the landing page URL. Only the referrer's host and the landing page's path are kept.",
                    "properties": {
                      "utm_source": {
                        "type": "string"
                      },
                      "utm_medium": {
                        "type": "string"
                      },
                      "utm_campaign": {
                        "type": "string"
                      },
                      "utm_term": {
                        "type": "string"
                      },
                      "utm_content": {
                        "type": "string"
                      },
                      "referrer": {
                        "type": "string"
                      },
                      "landing_page": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Verification email sent"
          },
          "400": {
            "description": "Invalid or disposable email, terms not accepted, or no Turnstile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Signup is not open, or the human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Bot protection or email is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup/verify": {
      "post": {
        "operationId": "signup_verify",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Confirm the email and get the free key",
        "security": [],
        "description": "Deprecated: use the dashboard on the product's website (sign in with your email). Kept working until it is retired. Step 2 of 2. Takes the token from the emailed link and creates the account with one Free key, shown once. One free key per verified email (Gmail dots and +tags count as the same address).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account and key created"
          },
          "400": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Link already used, or the email already has a key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Link expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "account_get",
        "tags": [
          "Account"
        ],
        "summary": "Your account: plans and usage per product, keys",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "products: for each product, your plan, its included monthly_units, hard_cap_units, units used this month and a usage_note in words. plan: the account-wide plan, id and name only (limits are per product). usage: this key's calls on every product. Free (0 units).",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/key/rotate": {
      "post": {
        "operationId": "account_key_rotate",
        "tags": [
          "Account"
        ],
        "summary": "Replace your key with a new one",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Issues a new secret for the same key id (the calling key unless key_id names another key of the account). The old secret stops working at once; usage and quota carry on. The new key is shown once. On an account created on the website a key can rotate only itself (403 manage_keys_on_dashboard): manage the other keys on the dashboard.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "properties": {
                  "key_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "key_id names another key of a self-serve account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Key not in this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/key/revoke": {
      "post": {
        "operationId": "account_key_revoke",
        "tags": [
          "Account"
        ],
        "summary": "Revoke a key",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "The key stops working at once (the calling key unless key_id names another key of the account; on an account created on the website a key can revoke only itself, 403 manage_keys_on_dashboard). To get a new key, sign in on the product's website and create one on the dashboard.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "additionalProperties": false,
                "properties": {
                  "key_id": {
                    "type": "string"
                  },
                  "confirm": {
                    "type": "boolean",
                    "const": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked"
          },
          "400": {
            "description": "confirm missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "key_id names another key of a self-serve account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/recover": {
      "post": {
        "operationId": "account_recover",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Lost your key: email a link to get a new one",
        "security": [],
        "description": "Deprecated: use the dashboard on the product's website (sign in with your email). Kept working until it is retired. A lost key: sign in on the website and create a new key. Checks Turnstile and per-IP and per-email limits. The 202 answer is the same whether or not the address has an account. The link is valid 15 minutes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "turnstile_token"
                ],
                "additionalProperties": false,
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048
                  },
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ],
                    "description": "The product site the request came from (the link points back to it; default: the product the account signed up on)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sent if the account exists"
          },
          "400": {
            "description": "Invalid email or no Turnstile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/recover/confirm": {
      "post": {
        "operationId": "account_recover_confirm",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Confirm the lost-key link and get a new key",
        "security": [],
        "description": "Deprecated: use the dashboard on the product's website (sign in with your email). Kept working until it is retired. Replaces the account's key secret (or reissues a key you revoked). Keys revoked by us are not reissued. The new key is shown once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key"
          },
          "400": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key revoked by us or account suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Link already used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Link expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "usage_get",
        "tags": [
          "Account"
        ],
        "summary": "Your usage this month",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Units and calls per endpoint for the calling key, and per product: your plan, units used, included monthly_units, hard_cap_units and a usage_note in words. plan: the account-wide plan, id and name only (limits are per product). Free (0 units).",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "operationId": "products_list",
        "tags": [
          "Account"
        ],
        "summary": "Products, stages and plans with prices",
        "security": [],
        "description": "One account and one key work across products; each product has its own plan (a free tier, or a paid subscription) and its own monthly units. Waitlist products are not sold yet. Each plan lists its included monthly_units and its hard_cap_units: above the hard cap, requests get 429 quota_exceeded until the next calendar month (UTC) or an upgrade. overage_charged is false: during the beta, usage between the two is not charged and no overage price is listed. usage_note says the same in words. Enterprise volumes and limits are by contract, and products not on sale yet have no prices: their prices, units and caps are null.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "operationId": "billing_checkout",
        "tags": [
          "Account"
        ],
        "summary": "Start a subscription for your account (Stripe Checkout)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "For an account with a key: creates a Stripe Checkout session for one product and plan and returns its URL (data.mode: live or sandbox). The plan is added to the account of the key; the key does not change. On the websites, people upgrade from their dashboard (signed in with their email; the legacy POST /v1/billing/subscribe is deprecated). With billing off the answer is 501 with what would be created.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "product",
                  "plan"
                ],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food"
                    ]
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "developer",
                      "pro"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout session created: data.url"
          },
          "400": {
            "description": "Unknown product or plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Product not sold yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already subscribed: use the billing portal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/subscribe": {
      "post": {
        "operationId": "billing_subscribe",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Buy a paid plan without a key (straight to Stripe Checkout)",
        "security": [],
        "description": "Retired: answers 410 endpoint_retired. Choose a plan on the product's website: it confirms the email with a code, then opens Stripe Checkout for the signed-in account. What it did: checks the Cloudflare Turnstile token, refuses disposable email domains and applies per-IP and per-email limits. For an address without an account: 201 with a Stripe Checkout URL for the product and plan (the page closes after 60 minutes). No account and no key exist until Stripe confirms the payment; an abandoned checkout creates nothing. data.claim is a one-time secret for the page the buyer returns to: keep it in the browser that pays (never in a URL) and send it to POST /v1/billing/claim after payment to see the API key once. For an address that already has an account: 202 link_sent, with no checkout. The mailbox gets a link (valid 30 minutes) that adds the plan to that account; its key stays the same. (With the account's key, POST /v1/billing/checkout does the same.) A newer checkout for the same address and product closes an older open one. Off unless SIGNUP_ENABLED; 501 while billing is off.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "product",
                  "plan",
                  "accept_terms",
                  "turnstile_token"
                ],
                "additionalProperties": false,
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "accept_terms": {
                    "type": "boolean",
                    "const": true
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "The token from the Turnstile widget on the subscribe page (action subscribe)"
                  },
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food"
                    ]
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "developer",
                      "pro"
                    ]
                  },
                  "attribution": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "As for POST /v1/signup: landing UTM tags, referrer and landing page",
                    "properties": {
                      "utm_source": {
                        "type": "string"
                      },
                      "utm_medium": {
                        "type": "string"
                      },
                      "utm_campaign": {
                        "type": "string"
                      },
                      "utm_term": {
                        "type": "string"
                      },
                      "utm_content": {
                        "type": "string"
                      },
                      "referrer": {
                        "type": "string"
                      },
                      "landing_page": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "New address: data.url (Stripe Checkout), data.session_id and data.claim (keep it in the browser)"
          },
          "202": {
            "description": "The address has an account: a link to continue was emailed to it (data.status link_sent)"
          },
          "400": {
            "description": "Invalid or disposable email, unknown product or plan, terms not accepted, or no Turnstile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Signup is not open, the product is not sold yet, or the human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Retired: choose a plan on the product's website",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Billing, bot protection or email is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/subscribe/confirm": {
      "post": {
        "operationId": "billing_subscribe_confirm",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "Continue from the emailed upgrade link to Stripe Checkout",
        "security": [],
        "description": "Retired: answers 410 endpoint_retired. Use the dashboard on the product's website (sign in with your email). What it did: took the token of the link that POST /v1/billing/subscribe emailed to an address with an account (the mailbox proved ownership) and opened Stripe Checkout for that account and its Stripe customer, with the same answer as POST /v1/billing/subscribe for a new address (url, session_id, claim).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout session created: data.url, data.session_id, data.claim"
          },
          "400": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Link already used, or the account already has a plan for this product (use the billing portal)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Retired (endpoint_retired), or the link expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/claim": {
      "post": {
        "operationId": "billing_claim",
        "deprecated": true,
        "tags": [
          "Account"
        ],
        "summary": "After payment: the plan, and the API key once",
        "security": [],
        "description": "Deprecated: use the dashboard on the product's website (sign in with your email). Kept working until it is retired. Called by the page Stripe returns to (it adds session_id to the address) with the claim secret from the same browser. 202 pending until the Stripe webhook has recorded the payment; then once: status key_issued with the api_key of the account the payment created (shown once, stored only as a hash), or status plan_active with no key when the plan went to an account that already existed (its key does not change). The claim works for 120 minutes after the checkout started. A key that was not saved: sign in on the website and create a key on the dashboard.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "session_id",
                  "claim"
                ],
                "additionalProperties": false,
                "properties": {
                  "session_id": {
                    "type": "string",
                    "description": "cs_… from the page address"
                  },
                  "claim": {
                    "type": "string",
                    "description": "data.claim of POST /v1/billing/subscribe or /subscribe/confirm"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "data.status key_issued (with data.api_key) or plan_active"
          },
          "202": {
            "description": "data.status pending: Stripe has not confirmed the payment yet; ask again in a few seconds"
          },
          "400": {
            "description": "Unknown claim, or one that belongs to another checkout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already shown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/portal": {
      "post": {
        "operationId": "billing_portal",
        "tags": [
          "Account"
        ],
        "summary": "Manage subscriptions (Stripe Customer Portal)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Returns a Customer Portal URL to change plan, cancel (at period end), update the card or download invoices. Without product, the portal of the product the account subscribes to.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Portal session created: data.url"
          },
          "400": {
            "description": "Unknown product, or no product named and no subscription to take it from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Product not sold yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No subscription yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/waitlist": {
      "post": {
        "operationId": "waitlist_join",
        "tags": [
          "Account"
        ],
        "summary": "Join a product's waitlist",
        "security": [],
        "description": "For products not sold yet. Same 202 answer whether or not the address is already on the list.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "product",
                  "email",
                  "consent"
                ],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ]
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "use_case": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "consent": {
                    "type": "boolean",
                    "const": true
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048
                  },
                  "attribution": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "On the waitlist"
          },
          "400": {
            "description": "Invalid email, product or missing consent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/client-errors": {
      "post": {
        "operationId": "client_errors",
        "tags": [
          "Account"
        ],
        "summary": "Report a browser error from one of our sites",
        "security": [],
        "description": "Used by the sites' error handler. Only the page path is kept (no query or fragment). Rate-limited per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "additionalProperties": false,
                "properties": {
                  "message": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "name": {
                    "type": "string"
                  },
                  "stack": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "page": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string"
                  },
                  "product": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "error",
                      "unhandledrejection"
                    ]
                  },
                  "release": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "description": "No message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/samples": {
      "get": {
        "operationId": "demo_samples",
        "tags": [
          "Demo"
        ],
        "summary": "Sample requests the demo answers",
        "security": [],
        "description": "The allow-listed sample requests per product and operation (from the product registry): the inputs the public demo answers without a key, and the values each sample may vary. meta.free_calculator: the operations and fields the free duty calculator may send for any HTS code, and its daily limit per IP address.",
        "parameters": [
          {
            "name": "product",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "tariffs",
                "food",
                "cosmetics"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/demo/v1/cosmetics/ingredients": {
      "get": {
        "operationId": "demo_cosmetics_ingredients",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_ingredients",
        "security": [],
        "summary": "Demo: Ingredient lookup by INCI, CAS or text",
        "description": "GET /v1/cosmetics/ingredients without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/ingredients instead.",
        "parameters": [
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Salicylic Acid"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/ingredients/{id}": {
      "get": {
        "operationId": "demo_cosmetics_ingredient",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_ingredient",
        "security": [],
        "summary": "Demo: Get a substance by id",
        "description": "GET /v1/cosmetics/ingredients/{id} without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/ingredients/{id} instead.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(cas|ec|inci|name|class):[^/]{1,190}$"
            },
            "example": "cas:69-72-7"
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/rules": {
      "get": {
        "operationId": "demo_cosmetics_rules",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_rules",
        "security": [],
        "summary": "Demo: Rules by market",
        "description": "GET /v1/cosmetics/rules without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/rules instead.",
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": true,
            "description": "EU, GB, US (federal) or a US state (state rules plus US federal)",
            "schema": {
              "type": "string",
              "enum": [
                "EU",
                "GB",
                "US",
                "US-CA",
                "US-CO",
                "US-MD",
                "US-ME",
                "US-MN",
                "US-OR",
                "US-VT",
                "US-WA"
              ]
            },
            "example": "EU"
          },
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Phenoxyethanol"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "prohibited",
                "restricted",
                "labelling_threshold",
                "allowed_colorant",
                "allowed_preservative",
                "allowed_uv_filter",
                "state_ban"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/compare": {
      "get": {
        "operationId": "demo_cosmetics_compare",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_compare",
        "security": [],
        "summary": "Demo: Compare an ingredient's rules across markets",
        "description": "GET /v1/cosmetics/compare without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/compare instead.",
        "parameters": [
          {
            "name": "inci",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Salicylic Acid"
          },
          {
            "name": "cas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "markets",
            "in": "query",
            "required": false,
            "description": "Comma-separated markets (default: all), e.g. EU,GB,US-CA",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "example": "EU,GB,US-CA"
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/categories": {
      "get": {
        "operationId": "demo_cosmetics_categories",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_categories",
        "security": [],
        "summary": "Demo: Product categories",
        "description": "GET /v1/cosmetics/categories without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/categories instead.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/formula-check": {
      "post": {
        "operationId": "demo_cosmetics_formula_check",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_formula_check",
        "security": [],
        "summary": "Demo: Check a formula against market rules",
        "description": "POST /v1/cosmetics/formula-check without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to POST /v1/cosmetics/formula-check instead.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_formula_check_body"
              },
              "example": {
                "markets": [
                  "EU",
                  "US-CA"
                ],
                "product_category": "body_lotion",
                "as_of": "2026-10-10",
                "ingredients": [
                  {
                    "inci": "Aqua",
                    "pct": 85
                  },
                  {
                    "inci": "Glycerin",
                    "pct": 5
                  },
                  {
                    "inci": "Phenoxyethanol",
                    "pct": 0.9,
                    "function": "preservative"
                  },
                  {
                    "inci": "Salicylic Acid",
                    "pct": 0.5,
                    "function": "preservative"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/formula-check/batch": {
      "post": {
        "operationId": "demo_cosmetics_formula_check_batch",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_formula_check_batch",
        "security": [],
        "summary": "Demo: Check many formulas",
        "description": "POST /v1/cosmetics/formula-check/batch without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to POST /v1/cosmetics/formula-check/batch instead.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_formula_check_batch_body"
              },
              "example": {
                "formulas": [
                  {
                    "id": "lotion",
                    "markets": [
                      "EU",
                      "GB"
                    ],
                    "product_category": "body_lotion",
                    "as_of": "2026-10-10",
                    "ingredients": [
                      {
                        "inci": "Aqua",
                        "pct": 85
                      },
                      {
                        "inci": "Glycerin",
                        "pct": 5
                      },
                      {
                        "inci": "Phenoxyethanol",
                        "pct": 0.9,
                        "function": "preservative"
                      }
                    ]
                  },
                  {
                    "id": "shampoo",
                    "markets": [
                      "EU",
                      "GB"
                    ],
                    "product_category": "shampoo",
                    "as_of": "2026-10-10",
                    "ingredients": [
                      {
                        "inci": "Aqua",
                        "pct": 80
                      },
                      {
                        "inci": "Sodium Laureth Sulfate",
                        "pct": 12
                      },
                      {
                        "inci": "Salicylic Acid",
                        "pct": 3,
                        "function": "anti_dandruff"
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/allergen-label": {
      "post": {
        "operationId": "demo_cosmetics_allergen_label",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_allergen_label",
        "security": [],
        "summary": "Demo: Allergen label calculation",
        "description": "POST /v1/cosmetics/allergen-label without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to POST /v1/cosmetics/allergen-label instead.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_allergen_label_body"
              },
              "example": {
                "markets": [
                  "EU",
                  "GB"
                ],
                "product_category": "body_lotion",
                "as_of": "2026-10-10",
                "fragrance": {
                  "dose_pct": 0.8,
                  "composition": [
                    {
                      "inci": "Linalool",
                      "pct_in_fragrance": 8
                    },
                    {
                      "inci": "Limonene",
                      "pct_in_fragrance": 5
                    },
                    {
                      "inci": "Geranial",
                      "pct_in_fragrance": 0.15
                    },
                    {
                      "inci": "Neral",
                      "pct_in_fragrance": 0.1
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/label-check": {
      "post": {
        "operationId": "demo_cosmetics_label_check",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_label_check",
        "security": [],
        "summary": "Demo: Validate an INCI ingredient list",
        "description": "POST /v1/cosmetics/label-check without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to POST /v1/cosmetics/label-check instead.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/cosmetics_label_check_body"
              },
              "example": {
                "market": "EU",
                "product_category": "body_lotion",
                "label": "Ingredients: Aqua, Glycerin, Fragrance, Phenoxyethanol, Titanium Dioxide (nano)",
                "formula": [
                  {
                    "inci": "Aqua",
                    "pct": 88
                  },
                  {
                    "inci": "Glycerin",
                    "pct": 9
                  },
                  {
                    "inci": "Fragrance",
                    "pct": 1
                  },
                  {
                    "inci": "Phenoxyethanol",
                    "pct": 0.9
                  },
                  {
                    "inci": "Titanium Dioxide",
                    "pct": 1.1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/demo/v1/cosmetics/changes": {
      "get": {
        "operationId": "demo_cosmetics_changes",
        "tags": [
          "Demo"
        ],
        "x-product": "cosmetics",
        "x-demo-of": "cosmetics_changes",
        "security": [],
        "summary": "Demo: Rule change feed",
        "description": "GET /v1/cosmetics/changes without a key, for the sample inputs that GET /demo/samples lists for it (every field equal to the sample's value or to one of its listed variants). The answer is the endpoint's real output, marked meta.demo = true, not metered. Limits per IP address: 30 sample requests a minute and 200 a day. Any other input answers 403 demo_not_allowed: send your API key to GET /v1/cosmetics/changes instead.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": true,
            "description": "YYYY-MM-DD",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-01-01"
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "EU, GB, US (federal) or a US state (state rules plus US federal)",
            "schema": {
              "type": "string",
              "enum": [
                "EU",
                "GB",
                "US",
                "US-CA",
                "US-CO",
                "US-MD",
                "US-ME",
                "US-MN",
                "US-OR",
                "US-VT",
                "US-WA"
              ]
            },
            "example": "EU"
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "in_force",
                "placing_on_market_ban_or_limit_applies",
                "last_day_non_compliant_stock_may_be_made_available",
                "pending_not_yet_in_force",
                "requirement_applies_from",
                "renumbered",
                "text_replaced"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "example": 20
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint's output, marked as a demo (meta.demo = true, meta.demo_kind sample or free_calculator; no usage)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Input not in the demo allow-list (demo_not_allowed), or the product is not open yet (product_not_available)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demo limit for this IP address reached",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit's window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Demo switched off, or dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": [
          "MCP"
        ],
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Model Context Protocol endpoint for AI agents. Methods: initialize, tools/list, tools/call, ping. Every REST operation above is a tool with the same input schema and the same metering.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification accepted"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "required": [
          "request_id",
          "dataset",
          "dataset_version",
          "data_status",
          "sources",
          "disclaimer"
        ],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "api_version": {
            "type": "string"
          },
          "dataset": {
            "type": "string",
            "enum": [
              "food",
              "tariffs",
              "cosmetics"
            ]
          },
          "dataset_version": {
            "type": "string"
          },
          "dataset_built_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "data_status": {
            "type": "string",
            "enum": [
              "released"
            ],
            "description": "released = served from the dataset's released build (dist/data.sqlite)"
          },
          "data_status_note": {
            "type": "string"
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "licence": {
                  "type": "string"
                },
                "commercial_redistribution_ok": {
                  "type": "boolean"
                }
              }
            }
          },
          "provenance_note": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string"
          },
          "usage": {
            "type": [
              "object",
              "null"
            ],
            "description": "Your usage on answers to your API key; null on demo answers.",
            "properties": {
              "plan": {
                "type": "string",
                "description": "Your plan on this product"
              },
              "product": {
                "type": "string"
              },
              "period": {
                "type": "string",
                "description": "Calendar month (UTC), YYYY-MM"
              },
              "units_charged": {
                "type": "integer",
                "description": "Units this call counted against your monthly units (a count, not money)"
              },
              "units_used": {
                "type": "integer",
                "description": "Units used on this product this month, including this call"
              },
              "monthly_units": {
                "type": "integer",
                "description": "Units included in your plan each month (the X-Quota-Limit header)"
              },
              "hard_cap_units": {
                "type": "integer",
                "description": "Requests are refused (429 quota_exceeded) above this many units in the month. Free plans: the included units; paid plans: a multiple of them"
              },
              "above_included_units": {
                "type": "boolean",
                "description": "This month's usage is above the included units (paid plans keep working up to hard_cap_units)"
              },
              "overage_charged": {
                "type": "boolean",
                "description": "Whether usage above the included units is charged. false during the beta"
              },
              "overage": {
                "type": "boolean",
                "deprecated": true,
                "description": "Older name of above_included_units, kept for existing clients. It is not a charge"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "food_barcode_batch_body": {
        "type": "object",
        "required": [
          "gtins"
        ],
        "additionalProperties": false,
        "properties": {
          "gtins": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string",
              "pattern": "^[0-9 -]{8,20}$"
            }
          }
        }
      },
      "food_analyze_body": {
        "type": "object",
        "required": [
          "lines"
        ],
        "additionalProperties": false,
        "properties": {
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "de",
              "it",
              "es"
            ],
            "default": "en",
            "description": "Language of the food names in the lines"
          },
          "servings": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "alternatives": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5,
            "default": 3
          }
        }
      },
      "food_meal_match_body": {
        "type": "object",
        "required": [
          "items"
        ],
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 30,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "label": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "What the food is, e.g. 'spaghetti bolognese' or '2 scrambled eggs'"
                },
                "grams": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "maximum": 5000
                },
                "quantity": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "maximum": 100
                },
                "unit": {
                  "type": "string",
                  "maxLength": 30,
                  "description": "cup, tbsp (T), tsp (t), slice, piece, serving, oz, g... (with quantity)"
                },
                "food_id": {
                  "type": "string",
                  "pattern": "^(fdc|ciqual|dish):[A-Za-z0-9_.-]{1,40}$",
                  "description": "A food you already know (skips matching)"
                },
                "gtin": {
                  "type": "string",
                  "pattern": "^[0-9 -]{8,20}$",
                  "description": "Barcode of a packaged food (label values)"
                }
              }
            }
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "de",
              "it",
              "es"
            ],
            "default": "en"
          },
          "alternatives": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5,
            "default": 3
          }
        }
      },
      "food_recipe_body": {
        "type": "object",
        "required": [
          "cooked",
          "ingredients"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "cooked": {
            "type": "boolean",
            "description": "false: no heat step (tier T0); true: cooked (T1-T3 from the references used)"
          },
          "servings": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          },
          "ingredients": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "type": "object",
              "required": [
                "food"
              ],
              "additionalProperties": false,
              "properties": {
                "food": {
                  "type": "string",
                  "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                  "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                },
                "grams": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "maximum": 100000,
                  "description": "Grams as written in the recipe (raw weight)"
                },
                "label": {
                  "type": "string",
                  "maxLength": 200
                },
                "convert": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "Enter the ingredient as a cooked record of the same food",
                  "properties": {
                    "to_cooked": {
                      "type": "string",
                      "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                      "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                    },
                    "yield": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "Weight raw -> cooked from a cited yield (else dry-matter balance between the two records)",
                      "properties": {
                        "fndds_moisture": {
                          "type": "integer",
                          "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                        },
                        "meat_yield_row": {
                          "type": "integer",
                          "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                        },
                        "fat": {
                          "type": "string",
                          "enum": [
                            "drained",
                            "kept"
                          ],
                          "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                        },
                        "ah102_item": {
                          "type": "integer",
                          "description": "Verified row of USDA Agriculture Handbook 102"
                        },
                        "evaporate_all_water": {
                          "type": "boolean",
                          "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                        },
                        "match": {
                          "type": "string",
                          "enum": [
                            "same_food",
                            "analog"
                          ],
                          "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                        },
                        "sr_refuse_portion": {
                          "type": "object",
                          "required": [
                            "food",
                            "seq"
                          ],
                          "additionalProperties": false,
                          "properties": {
                            "food": {
                              "type": "string",
                              "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                              "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                            },
                            "seq": {
                              "type": "integer",
                              "minimum": 1
                            }
                          },
                          "description": "SR 'yield from 1 lb raw' portion"
                        }
                      }
                    },
                    "edible_ah102": {
                      "type": "integer",
                      "description": "AH-102 item for the edible portion (refuse removed)"
                    },
                    "match": {
                      "type": "string",
                      "enum": [
                        "same_food",
                        "analog"
                      ]
                    }
                  }
                },
                "cook": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
                  "properties": {
                    "fndds_moisture": {
                      "type": "integer",
                      "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                    },
                    "meat_yield_row": {
                      "type": "integer",
                      "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                    },
                    "fat": {
                      "type": "string",
                      "enum": [
                        "drained",
                        "kept"
                      ],
                      "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                    },
                    "ah102_item": {
                      "type": "integer",
                      "description": "Verified row of USDA Agriculture Handbook 102"
                    },
                    "evaporate_all_water": {
                      "type": "boolean",
                      "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                    },
                    "match": {
                      "type": "string",
                      "enum": [
                        "same_food",
                        "analog"
                      ],
                      "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                    }
                  }
                },
                "retention_code": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "USDA Release 6 nutrient retention code (minerals; alcohol with its energy)"
                },
                "grams_rule": {
                  "type": "object",
                  "required": [
                    "fndds_ratio"
                  ],
                  "additionalProperties": false,
                  "description": "Grams from an FNDDS recipe ratio (e.g. water absorbed per gram of rice)",
                  "properties": {
                    "fndds_ratio": {
                      "type": "object",
                      "required": [
                        "food_code",
                        "ingredient_codes",
                        "per_codes",
                        "of"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "food_code": {
                          "type": "integer"
                        },
                        "ingredient_codes": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        },
                        "per_codes": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        },
                        "of": {
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "description": "0-based indexes of earlier ingredients the ratio applies to"
                        }
                      }
                    },
                    "match": {
                      "type": "string",
                      "enum": [
                        "same_food",
                        "analog"
                      ]
                    }
                  }
                }
              }
            }
          },
          "hydrate": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "object",
              "required": [
                "members",
                "like"
              ],
              "additionalProperties": false,
              "properties": {
                "members": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "like": {
                  "type": "string",
                  "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                  "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                },
                "match": {
                  "type": "string",
                  "enum": [
                    "same_food",
                    "analog"
                  ]
                }
              }
            },
            "description": "Bring a group of ingredients to the water content of a cooked record (e.g. a dough)"
          },
          "group_cook": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "object",
              "required": [
                "members",
                "cook"
              ],
              "additionalProperties": false,
              "properties": {
                "members": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "cook": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
                  "properties": {
                    "fndds_moisture": {
                      "type": "integer",
                      "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                    },
                    "meat_yield_row": {
                      "type": "integer",
                      "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                    },
                    "fat": {
                      "type": "string",
                      "enum": [
                        "drained",
                        "kept"
                      ],
                      "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                    },
                    "ah102_item": {
                      "type": "integer",
                      "description": "Verified row of USDA Agriculture Handbook 102"
                    },
                    "evaporate_all_water": {
                      "type": "boolean",
                      "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                    },
                    "match": {
                      "type": "string",
                      "enum": [
                        "same_food",
                        "analog"
                      ],
                      "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                    }
                  }
                }
              }
            },
            "description": "Weight change of a group of ingredients (e.g. a sauce reduced)"
          },
          "dish_cook": {
            "type": "object",
            "additionalProperties": false,
            "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
            "properties": {
              "fndds_moisture": {
                "type": "integer",
                "description": "FNDDS 2021-2023 food code: its recipe moisture change"
              },
              "meat_yield_row": {
                "type": "integer",
                "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
              },
              "fat": {
                "type": "string",
                "enum": [
                  "drained",
                  "kept"
                ],
                "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
              },
              "ah102_item": {
                "type": "integer",
                "description": "Verified row of USDA Agriculture Handbook 102"
              },
              "evaporate_all_water": {
                "type": "boolean",
                "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
              },
              "match": {
                "type": "string",
                "enum": [
                  "same_food",
                  "analog"
                ],
                "description": "analog: the yield of a similar food (flags the result T3 estimated)"
              }
            }
          },
          "uncovered_heat_steps": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Heat steps you know of that have no USDA yield (flags the result T3)"
          },
          "retention_code": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "tariffs_duty_stack_body": {
        "type": "object",
        "required": [
          "hts10",
          "origin",
          "entry_date"
        ],
        "additionalProperties": false,
        "properties": {
          "hts10": {
            "type": "string",
            "pattern": "^[0-9.]{10,13}$",
            "description": "10-digit HTSUS statistical number"
          },
          "origin": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$",
            "description": "Country of origin, ISO 3166-1 alpha-2"
          },
          "entry_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "export_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "mode": {
            "type": "string",
            "enum": [
              "vessel",
              "air",
              "truck",
              "rail",
              "mail",
              "postal",
              "other"
            ]
          },
          "declared_attributes": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "description": "Facts you declare, e.g. steel_content_value_share, usmca_qualifying"
          },
          "entered_value_usd": {
            "type": [
              "number",
              "string"
            ],
            "description": "Entered (customs) value in USD: adds the duty in dollars (amounts)"
          }
        }
      },
      "tariffs_duty_stack_batch_body": {
        "type": "object",
        "required": [
          "lines"
        ],
        "additionalProperties": false,
        "properties": {
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "hts10",
                "origin",
                "entry_date"
              ],
              "properties": {
                "line_id": {
                  "type": "string",
                  "maxLength": 40
                },
                "hts10": {
                  "type": "string",
                  "pattern": "^[0-9.]{10,13}$",
                  "description": "10-digit HTSUS statistical number"
                },
                "origin": {
                  "type": "string",
                  "pattern": "^[A-Za-z]{2}$"
                },
                "entry_date": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "export_date": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "vessel",
                    "air",
                    "truck",
                    "rail",
                    "mail",
                    "postal",
                    "other"
                  ]
                },
                "declared_attributes": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ]
                  }
                },
                "entered_value_usd": {
                  "type": [
                    "number",
                    "string"
                  ],
                  "description": "Entered (customs) value in USD: adds the duty in dollars (amounts)"
                }
              }
            }
          }
        }
      },
      "tariffs_compare_origins_body": {
        "type": "object",
        "required": [
          "hts10",
          "origins",
          "entry_date"
        ],
        "additionalProperties": false,
        "properties": {
          "hts10": {
            "type": "string",
            "pattern": "^[0-9.]{10,13}$"
          },
          "origins": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$"
            }
          },
          "entry_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "export_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "mode": {
            "type": "string",
            "enum": [
              "vessel",
              "air",
              "truck",
              "rail",
              "mail",
              "postal",
              "other"
            ]
          },
          "declared_attributes": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            }
          },
          "entered_value_usd": {
            "type": [
              "number",
              "string"
            ],
            "description": "Entered (customs) value in USD: adds the duty in dollars (amounts)"
          }
        }
      },
      "tariffs_ieepa_refund_body": {
        "type": "object",
        "required": [
          "lines"
        ],
        "additionalProperties": false,
        "properties": {
          "as_of": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "hts10",
                "origin",
                "entry_date",
                "entered_value_usd"
              ],
              "properties": {
                "line_id": {
                  "type": "string",
                  "maxLength": 40
                },
                "entry_number": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20
                },
                "line_number": {
                  "type": "integer",
                  "minimum": 1
                },
                "hts10": {
                  "type": "string",
                  "pattern": "^[0-9.]{10,13}$",
                  "description": "10-digit HTSUS number from the entry summary (you supply it; we do not classify)"
                },
                "origin": {
                  "type": "string",
                  "pattern": "^[A-Za-z]{2}$"
                },
                "entry_date": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "export_date": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "vessel",
                    "air",
                    "truck",
                    "rail",
                    "mail",
                    "postal",
                    "other"
                  ]
                },
                "entered_value_usd": {
                  "type": [
                    "number",
                    "string"
                  ],
                  "description": "Entered (customs) value in USD"
                },
                "declared_attributes": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "string",
                      "number",
                      "boolean"
                    ]
                  }
                },
                "ieepa_duty_paid_usd": {
                  "type": [
                    "number",
                    "string"
                  ],
                  "description": "IEEPA duty actually paid on the line (ACE ES-003), if known"
                },
                "liquidation_status": {
                  "type": "string",
                  "enum": [
                    "unliquidated",
                    "liquidated",
                    "extended",
                    "suspended",
                    "under_review"
                  ]
                },
                "liquidation_date": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "reconciliation_flag": {
                  "type": "boolean"
                },
                "reconciliation_entry_on_file": {
                  "type": "boolean"
                },
                "entry_type": {
                  "type": "string",
                  "pattern": "^\\d{1,2}$",
                  "description": "CBP entry type code, e.g. 01, 11, 21"
                },
                "ace_filed": {
                  "type": "boolean"
                },
                "drawback_designated": {
                  "type": "boolean"
                },
                "open_protest": {
                  "type": "boolean"
                },
                "adcvd_pending": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "cosmetics_formula_check_body": {
        "type": "object",
        "required": [
          "markets",
          "product_category",
          "ingredients"
        ],
        "additionalProperties": false,
        "properties": {
          "markets": {
            "type": "array",
            "minItems": 1,
            "maxItems": 11,
            "items": {
              "type": "string",
              "enum": [
                "EU",
                "GB",
                "US",
                "US-CA",
                "US-CO",
                "US-MD",
                "US-ME",
                "US-MN",
                "US-OR",
                "US-VT",
                "US-WA"
              ],
              "description": "EU, GB, US (federal) or a US state (state rules plus US federal)"
            }
          },
          "product_category": {
            "type": "string",
            "maxLength": 64,
            "description": "GET /v1/cosmetics/categories"
          },
          "as_of": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "mode": {
            "type": "string",
            "enum": [
              "placing",
              "making_available"
            ],
            "default": "placing"
          },
          "children_under_3": {
            "type": "boolean"
          },
          "professional_use": {
            "type": "boolean"
          },
          "mixing_ratio": {
            "type": "string",
            "pattern": "^\\s*\\d+(\\.\\d+)?\\s*:\\s*\\d+(\\.\\d+)?\\s*$",
            "description": "Oxidative hair or eyelash dye: product:developer, e.g. '1:1'"
          },
          "formaldehyde_released_pct": {
            "type": [
              "number",
              "string",
              "null"
            ],
            "description": "Maximum theoretical releasable formaldehyde of the whole product, from every source, percent w/w. Settles the 'releases formaldehyde' label and the Annex II bans that apply only at or above 0,1 % (II/1605-1607). null or omitted = not known: never treated as 0"
          },
          "contaminants_ppm": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "number",
                "string"
              ]
            },
            "description": "Measured contaminants, e.g. {\"lead\": 2, \"1,4-dioxane\": 1}"
          },
          "ingredients": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "inci": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "INCI name as on the label"
                },
                "cas": {
                  "type": "string",
                  "pattern": "^\\d{2,7}-\\d{2}-\\d$",
                  "description": "CAS number"
                },
                "ec": {
                  "type": "string",
                  "pattern": "^\\d{3}-\\d{3}-\\d$",
                  "description": "EC (EINECS / ELINCS) number"
                },
                "name": {
                  "type": "string",
                  "maxLength": 200,
                  "description": "Other name (chemical name)"
                },
                "pct": {
                  "type": [
                    "number",
                    "string",
                    "null"
                  ],
                  "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                },
                "function": {
                  "type": "string",
                  "maxLength": 40,
                  "description": "What the ingredient does in this formula (e.g. preservative, uv_filter, colorant, ph_adjuster, solvent, denaturant, fragrance_ingredient). Settles entries tied to a use; if omitted they come back NEEDS_ATTENTION."
                },
                "basis": {
                  "type": "string",
                  "maxLength": 60,
                  "description": "Basis of pct when a limit is 'calculated as' something (e.g. 'free base', 'zinc')"
                },
                "intentionally_added": {
                  "type": "boolean",
                  "description": "false for trace/contaminant presence (threshold rules)"
                },
                "flags": {
                  "type": "array",
                  "maxItems": 5,
                  "items": {
                    "type": "string",
                    "enum": [
                      "pfas",
                      "ortho_phthalate",
                      "formaldehyde_releaser"
                    ]
                  },
                  "description": "Class membership you know (class bans cannot be settled from a name)"
                }
              }
            }
          }
        }
      },
      "cosmetics_formula_check_batch_body": {
        "type": "object",
        "required": [
          "formulas"
        ],
        "additionalProperties": false,
        "properties": {
          "formulas": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "markets",
                "product_category",
                "ingredients"
              ],
              "additionalProperties": false,
              "properties": {
                "id": {
                  "type": "string",
                  "maxLength": 60
                },
                "markets": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 11,
                  "items": {
                    "type": "string",
                    "enum": [
                      "EU",
                      "GB",
                      "US",
                      "US-CA",
                      "US-CO",
                      "US-MD",
                      "US-ME",
                      "US-MN",
                      "US-OR",
                      "US-VT",
                      "US-WA"
                    ],
                    "description": "EU, GB, US (federal) or a US state (state rules plus US federal)"
                  }
                },
                "product_category": {
                  "type": "string",
                  "maxLength": 64
                },
                "as_of": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "description": "YYYY-MM-DD"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "placing",
                    "making_available"
                  ]
                },
                "children_under_3": {
                  "type": "boolean"
                },
                "professional_use": {
                  "type": "boolean"
                },
                "mixing_ratio": {
                  "type": "string",
                  "pattern": "^\\s*\\d+(\\.\\d+)?\\s*:\\s*\\d+(\\.\\d+)?\\s*$"
                },
                "formaldehyde_released_pct": {
                  "type": [
                    "number",
                    "string",
                    "null"
                  ],
                  "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                },
                "contaminants_ppm": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "number",
                      "string"
                    ]
                  }
                },
                "ingredients": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 200,
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "inci": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200,
                        "description": "INCI name as on the label"
                      },
                      "cas": {
                        "type": "string",
                        "pattern": "^\\d{2,7}-\\d{2}-\\d$",
                        "description": "CAS number"
                      },
                      "ec": {
                        "type": "string",
                        "pattern": "^\\d{3}-\\d{3}-\\d$",
                        "description": "EC (EINECS / ELINCS) number"
                      },
                      "name": {
                        "type": "string",
                        "maxLength": 200,
                        "description": "Other name (chemical name)"
                      },
                      "pct": {
                        "type": [
                          "number",
                          "string",
                          "null"
                        ],
                        "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                      },
                      "function": {
                        "type": "string",
                        "maxLength": 40,
                        "description": "What the ingredient does in this formula (e.g. preservative, uv_filter, colorant, ph_adjuster, solvent, denaturant, fragrance_ingredient). Settles entries tied to a use; if omitted they come back NEEDS_ATTENTION."
                      },
                      "basis": {
                        "type": "string",
                        "maxLength": 60,
                        "description": "Basis of pct when a limit is 'calculated as' something (e.g. 'free base', 'zinc')"
                      },
                      "intentionally_added": {
                        "type": "boolean",
                        "description": "false for trace/contaminant presence (threshold rules)"
                      },
                      "flags": {
                        "type": "array",
                        "maxItems": 5,
                        "items": {
                          "type": "string",
                          "enum": [
                            "pfas",
                            "ortho_phthalate",
                            "formaldehyde_releaser"
                          ]
                        },
                        "description": "Class membership you know (class bans cannot be settled from a name)"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "cosmetics_allergen_label_body": {
        "type": "object",
        "required": [
          "markets"
        ],
        "additionalProperties": false,
        "properties": {
          "markets": {
            "type": "array",
            "minItems": 1,
            "maxItems": 2,
            "items": {
              "type": "string",
              "enum": [
                "EU",
                "GB"
              ]
            }
          },
          "product_category": {
            "type": "string",
            "maxLength": 64
          },
          "exposure": {
            "type": "string",
            "enum": [
              "leave_on",
              "rinse_off"
            ],
            "description": "If no product_category"
          },
          "as_of": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "fragrance": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "dose_pct": {
                "type": [
                  "number",
                  "string",
                  "null"
                ],
                "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
              },
              "composition": {
                "type": "array",
                "maxItems": 300,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "inci": {
                      "type": "string",
                      "maxLength": 200
                    },
                    "name": {
                      "type": "string",
                      "maxLength": 200
                    },
                    "cas": {
                      "type": "string",
                      "maxLength": 20
                    },
                    "pct_in_fragrance": {
                      "type": [
                        "number",
                        "string",
                        "null"
                      ],
                      "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                    }
                  }
                }
              }
            }
          },
          "allergens": {
            "type": "array",
            "maxItems": 300,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "inci": {
                  "type": "string",
                  "maxLength": 200
                },
                "name": {
                  "type": "string",
                  "maxLength": 200
                },
                "cas": {
                  "type": "string",
                  "maxLength": 20
                },
                "pct_in_product": {
                  "type": [
                    "number",
                    "string",
                    "null"
                  ],
                  "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                }
              }
            }
          }
        }
      },
      "cosmetics_label_check_body": {
        "type": "object",
        "required": [
          "market",
          "label"
        ],
        "additionalProperties": false,
        "properties": {
          "market": {
            "type": "string",
            "enum": [
              "EU",
              "GB",
              "US",
              "US-CA",
              "US-CO",
              "US-MD",
              "US-ME",
              "US-MN",
              "US-OR",
              "US-VT",
              "US-WA"
            ],
            "description": "EU, GB, US (federal) or a US state (state rules plus US federal)"
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000,
            "description": "Ingredient list as printed, e.g. 'Ingredients: Aqua, Glycerin, ...'"
          },
          "product_category": {
            "type": "string",
            "maxLength": 64
          },
          "as_of": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "children_under_3": {
            "type": "boolean"
          },
          "formula": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "inci": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "INCI name as on the label"
                },
                "cas": {
                  "type": "string",
                  "pattern": "^\\d{2,7}-\\d{2}-\\d$",
                  "description": "CAS number"
                },
                "ec": {
                  "type": "string",
                  "pattern": "^\\d{3}-\\d{3}-\\d$",
                  "description": "EC (EINECS / ELINCS) number"
                },
                "name": {
                  "type": "string",
                  "maxLength": 200,
                  "description": "Other name (chemical name)"
                },
                "pct": {
                  "type": [
                    "number",
                    "string",
                    "null"
                  ],
                  "description": "Percent w/w (number, or a numeric string). null or omitted = not known: never treated as 0"
                },
                "function": {
                  "type": "string",
                  "maxLength": 40,
                  "description": "What the ingredient does in this formula (e.g. preservative, uv_filter, colorant, ph_adjuster, solvent, denaturant, fragrance_ingredient). Settles entries tied to a use; if omitted they come back NEEDS_ATTENTION."
                },
                "basis": {
                  "type": "string",
                  "maxLength": 60,
                  "description": "Basis of pct when a limit is 'calculated as' something (e.g. 'free base', 'zinc')"
                },
                "intentionally_added": {
                  "type": "boolean",
                  "description": "false for trace/contaminant presence (threshold rules)"
                },
                "flags": {
                  "type": "array",
                  "maxItems": 5,
                  "items": {
                    "type": "string",
                    "enum": [
                      "pfas",
                      "ortho_phthalate",
                      "formaldehyde_releaser"
                    ]
                  },
                  "description": "Class membership you know (class bans cannot be settled from a name)"
                }
              }
            },
            "description": "Concentrations (enables the order and allergen checks)"
          }
        }
      }
    }
  }
}
