{
  "openapi": "3.0.3",
  "info": {
    "title": "SNRL API",
    "version": "1.0.0",
    "description": "A read-only REST API over the free SNRL chemical database — search chemicals, fetch products and their GHS hazards. Every request needs an API key in the `X-API-Key` header; create one free in the Developer Console at https://snrl.in/console.html.\n\nRate limits: a short-window per-key burst limit plus a daily quota of 5,000 calls per key (resets 00:00 UTC).",
    "contact": {"name": "SNRL", "url": "https://snrl.in/docs.html"}
  },
  "servers": [{"url": "https://snrl.in/v1", "description": "Production"}],
  "security": [{"ApiKeyAuth": []}],
  "tags": [
    {"name": "search", "description": "Full-text and type-ahead search (scope read:search)"},
    {"name": "substances", "description": "The authoritative chemical registry: identity + GHS classification (scope read:search)"},
    {"name": "products", "description": "Product records and SDS version history (scope read:products)"}
  ],
  "paths": {
    "/search": {
      "get": {
        "tags": ["search"],
        "summary": "Search products",
        "description": "Full-text search over products. A CAS number, UN number, or H-code routes to an exact match; free text is fuzzy over names, synonyms and ingredients. Returns hits plus facets.",
        "parameters": [
          {"name": "q", "in": "query", "schema": {"type": "string"}, "description": "Search text, CAS (e.g. 67-64-1), or hazard code (e.g. H225)", "example": "toluene"},
          {"name": "hazard_code", "in": "query", "schema": {"type": "string"}, "description": "Filter by GHS H-code", "example": "H225"},
          {"name": "signal_word", "in": "query", "schema": {"type": "string", "enum": ["Danger", "Warning"]}, "description": "Filter by GHS signal word"},
          {"name": "manufacturer_id", "in": "query", "schema": {"type": "string"}, "description": "Filter by manufacturer id"},
          {"name": "category", "in": "query", "schema": {"type": "string"}},
          {"name": "country", "in": "query", "schema": {"type": "string"}, "description": "ISO 3166-1 alpha-2"},
          {"name": "language", "in": "query", "schema": {"type": "string"}},
          {"name": "sort", "in": "query", "schema": {"type": "string", "enum": ["relevance", "name", "revision_date", "manufacturer"], "default": "relevance"}},
          {"name": "page", "in": "query", "schema": {"type": "integer", "minimum": 1, "default": 1}},
          {"name": "size", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}}
        ],
        "responses": {
          "200": {"description": "Search results", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchResponse"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/Forbidden"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/search/suggest": {
      "get": {
        "tags": ["search"],
        "summary": "Type-ahead suggestions",
        "description": "Autocomplete suggestions for a partial query. Returns an array of suggestion strings.",
        "parameters": [
          {"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 1}, "description": "Partial query text", "example": "aceto"},
          {"name": "size", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 25, "default": 10}}
        ],
        "responses": {
          "200": {"description": "Suggestions", "content": {"application/json": {"schema": {"type": "array", "items": {"type": "string"}}, "example": ["acetone", "acetonitrile", "acetophenone"]}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/substances": {
      "get": {
        "tags": ["substances"],
        "summary": "Search the substance registry",
        "description": "Search the ~20k authoritative chemical registry (ECHA Annex VI + PubChem) by name/synonym (fuzzy) or exact CAS/H-code, with hazard/use/HPV filters and facets. Each hit carries a provenance badge (harmonised vs. aggregated). Scope: read:search.",
        "parameters": [
          {"name": "q", "in": "query", "schema": {"type": "string"}, "description": "Name, synonym, CAS, or H-code", "example": "toluene"},
          {"name": "hazard_code", "in": "query", "schema": {"type": "string"}, "description": "Filter by GHS H-code", "example": "H350"},
          {"name": "pictogram", "in": "query", "schema": {"type": "string"}, "description": "Filter by GHS pictogram code", "example": "GHS08"},
          {"name": "use", "in": "query", "schema": {"type": "string"}, "description": "Filter by EPA CDR use tag"},
          {"name": "hpv", "in": "query", "schema": {"type": "boolean"}, "description": "Only High Production Volume substances"},
          {"name": "size", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}}
        ],
        "responses": {
          "200": {"description": "Substance hits + facets", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SubstanceSearchResponse"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/Forbidden"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/substances/{cas}": {
      "get": {
        "tags": ["substances"],
        "summary": "Get a substance by CAS",
        "description": "A single substance's authoritative identity + GHS classification and provenance, keyed by CAS number. Scope: read:search.",
        "parameters": [{"name": "cas", "in": "path", "required": true, "schema": {"type": "string"}, "example": "108-88-3"}],
        "responses": {
          "200": {"description": "Substance", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Substance"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"description": "Not found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      }
    },
    "/products": {
      "get": {
        "tags": ["products"],
        "summary": "List products",
        "description": "List products, newest first, cursor-paginated.",
        "parameters": [
          {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50}},
          {"name": "cursor", "in": "query", "schema": {"type": "string"}, "description": "Opaque cursor from a prior next_cursor"}
        ],
        "responses": {
          "200": {"description": "A page of products", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductList"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/Forbidden"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/products/{id}": {
      "get": {
        "tags": ["products"],
        "summary": "Get a product",
        "description": "A product and its active version — identity, GHS signal word / pictograms / H-statements, composition (CAS + concentration), the canonical substance GHS, and the source SDS link.",
        "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}],
        "responses": {
          "200": {"description": "Product detail", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Product"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"description": "Not found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      }
    },
    "/products/{id}/versions": {
      "get": {
        "tags": ["products"],
        "summary": "Product SDS version history",
        "description": "Every SDS revision captured for a product (append-only history).",
        "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}],
        "responses": {
          "200": {"description": "Version history", "content": {"application/json": {"schema": {"type": "array", "items": {"type": "object"}}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"description": "Not found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key", "description": "API key from the Developer Console (shown once at creation)."}
    },
    "responses": {
      "Unauthorized": {"description": "Missing, invalid, or revoked key", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Forbidden": {"description": "Key lacks the required scope", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "RateLimited": {"description": "Rate limit or daily quota exceeded (see Retry-After header)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
    },
    "schemas": {
      "Error": {"type": "object", "properties": {"detail": {"type": "string"}}, "example": {"detail": "invalid API key"}},
      "Facet": {"type": "object", "properties": {"value": {"type": "string"}, "count": {"type": "integer"}}},
      "Provenance": {
        "type": "object",
        "description": "How authoritative this classification is",
        "properties": {
          "tier": {"type": "string", "enum": ["harmonised", "aggregated", "identity", "unknown"]},
          "label": {"type": "string"},
          "authority": {"type": "string"},
          "note": {"type": "string"}
        }
      },
      "Substance": {
        "type": "object",
        "properties": {
          "cas": {"type": "string"},
          "name": {"type": "string", "nullable": true},
          "iupac_name": {"type": "string", "nullable": true},
          "synonyms": {"type": "array", "items": {"type": "string"}},
          "formula": {"type": "string", "nullable": true},
          "molecular_weight": {"type": "number", "nullable": true},
          "signal_word": {"type": "string", "nullable": true},
          "pictograms": {"type": "array", "items": {"type": "string"}},
          "hazard_codes": {"type": "array", "items": {"type": "string"}},
          "pubchem_cid": {"type": "integer", "nullable": true},
          "hpv": {"type": "boolean"},
          "uses": {"type": "array", "items": {"type": "string"}},
          "source": {"type": "string", "nullable": true},
          "exposure_limits": {"type": "object", "nullable": true, "description": "NIOSH REL / OSHA PEL / IDLH occupational exposure limits (NIOSH Pocket Guide), where available", "properties": {"rel": {"type": "string"}, "pel": {"type": "string"}, "idlh": {"type": "string"}}},
          "provenance": {"$ref": "#/components/schemas/Provenance"}
        }
      },
      "SubstanceSearchResponse": {
        "type": "object",
        "properties": {
          "total": {"type": "integer"},
          "size": {"type": "integer"},
          "items": {"type": "array", "items": {"$ref": "#/components/schemas/Substance"}},
          "facets": {"type": "object", "additionalProperties": {"type": "array", "items": {"$ref": "#/components/schemas/Facet"}}}
        }
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "total": {"type": "integer"},
          "page": {"type": "integer"},
          "size": {"type": "integer"},
          "items": {"type": "array", "items": {"type": "object"}, "description": "Product hits (identity, CAS, GHS signal word / pictograms / hazard codes, manufacturer)"},
          "facets": {"type": "object", "additionalProperties": {"type": "array", "items": {"$ref": "#/components/schemas/Facet"}}}
        }
      },
      "ProductList": {
        "type": "object",
        "properties": {
          "items": {"type": "array", "items": {"type": "object"}},
          "next_cursor": {"type": "string", "nullable": true, "description": "Pass as ?cursor= to fetch the next page; null when exhausted"}
        }
      },
      "Product": {
        "type": "object",
        "description": "Product identity + active SDS version. Fields include id, name, product_code, manufacturer, cas, signal_word, pictograms, hazards[], ingredients[], source_url, and substance (canonical registry GHS).",
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "name": {"type": "string"},
          "cas": {"type": "string", "nullable": true},
          "signal_word": {"type": "string", "nullable": true},
          "pictograms": {"type": "array", "items": {"type": "string"}},
          "hazards": {"type": "array", "items": {"type": "object"}},
          "source_url": {"type": "string", "description": "Official manufacturer SDS URL"}
        }
      }
    }
  }
}
