{
  "openapi": "3.1.0",
  "info": {
    "title": "Floor Sanding Australia - Architect Hub API",
    "version": "1.0.0",
    "summary": "Retrieval, cited synthesis and clause-generation API over the Bona / ATFA / NCC corpus.",
    "description": "Public read-only API that powers the Architect Hub at\n`https://floorsandingaustralia.com/architects/`. The corpus covers 18 Bona\nproducts, 150+ technical PDFs, 28 third-party certificates and 165 FAQ\nentries. Every response is citation-backed.\n\nEndpoints:\n  - `GET /architects/api/search` - vector search, top-k chunks\n  - `POST /architects/api/ask` - cited synthesis via Haiku 4.5\n  - `POST /architects/api/ask/stream` - Server-Sent Events stream\n  - `GET  /architects/api/ask/health` - liveness for the synthesis layer\n  - `POST /architects/api/clause` - generates a MasterFormat/NATSPEC/NBS clause\n\n**MCP endpoints.** A Model Context Protocol server is also hosted at\n`https://mcp.floorsandingaustralia.com`. It is not part of this OpenAPI\ndocument. For the full tool list and connection details see the\n`REMOTE_SETUP.md` file in the MCP repository.\n\n**Rate limiting.** Default limit is **60 requests per minute per IP**.\nRate limit state is reflected on every response via the `X-RateLimit-*`\nheaders listed on each endpoint. The synthesis endpoints apply an\nadditional `10/minute` + `3/second` per-IP burst cap.\n\n**Auth.** All endpoints are public. An optional `Bearer` token is accepted\nfor future elevated rate limits - it is currently a no-op.\n",
    "contact": {
      "name": "Floor Sanding Australia",
      "email": "info@sand-aid.com",
      "url": "https://floorsandingaustralia.com/"
    },
    "license": {
      "name": "CC-BY-4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    },
    "termsOfService": "https://floorsandingaustralia.com/"
  },
  "servers": [
    {
      "url": "https://floorsandingaustralia.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "MCP server setup (REMOTE_SETUP.md)",
    "url": "https://mcp.floorsandingaustralia.com/"
  },
  "tags": [
    {
      "name": "search",
      "description": "Vector retrieval over the Bona / ATFA / NCC corpus."
    },
    {
      "name": "ask",
      "description": "Cited AI synthesis (Anthropic Haiku 4.5) on top of the retrieval layer."
    },
    {
      "name": "clause",
      "description": "Generates a NATSPEC / MasterFormat / NBS specification clause."
    },
    {
      "name": "health",
      "description": "Liveness checks."
    }
  ],
  "security": [
    {},
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/architects/api/search": {
      "get": {
        "tags": [
          "search"
        ],
        "summary": "Top-k vector search",
        "description": "Retrieves the top-k matching chunks from the Bona corpus using the\nquery embedding (Ollama on heartbeat) and an sqlite-vec ANN index.\nFilters are AND-combined.\n",
        "operationId": "searchCorpus",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The search query.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500
            },
            "example": "VOC of Bona Traffic HD"
          },
          {
            "name": "k",
            "in": "query",
            "required": false,
            "description": "Number of results to return (1-25, default 5).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 5
            }
          },
          {
            "name": "product",
            "in": "query",
            "required": false,
            "description": "Restrict to a single product slug, e.g. `traffic-hd`.",
            "schema": {
              "type": "string"
            },
            "example": "traffic-hd"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Restrict to a source type.",
            "schema": {
              "type": "string",
              "enum": [
                "product",
                "pdf",
                "cert",
                "faq",
                "compatibility_matrix"
              ]
            }
          },
          {
            "name": "cert_type",
            "in": "query",
            "required": false,
            "description": "Restrict to a certificate family.",
            "schema": {
              "type": "string",
              "enum": [
                "as4586-slip",
                "din51130-slip",
                "dibt",
                "emicode",
                "en13501-fire",
                "en14904-sport",
                "en71-toy-safety",
                "epd",
                "greenguard",
                "iso14001"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                },
                "examples": {
                  "voc": {
                    "summary": "VOC query, top 2 results",
                    "value": {
                      "ok": true,
                      "query": "VOC of Bona Traffic HD",
                      "k": 2,
                      "filters": {},
                      "elapsed_ms": 142.3,
                      "count": 2,
                      "results": [
                        {
                          "score": 0.891,
                          "snippet": "Bona Traffic HD: VOC 130 g/L, solids 33%, two-component waterborne polyurethane...",
                          "citation": "Bona Traffic HD TDS (2024), p.1",
                          "source_type": "pdf",
                          "product_slug": "traffic-hd",
                          "cert_type": null,
                          "doc_title": "Bona Traffic HD Technical Data Sheet",
                          "source_path": "static/bona-pdfs/bona-traffic-hd-tds.pdf",
                          "product_url": "/architects/products/traffic-hd.html"
                        },
                        {
                          "score": 0.812,
                          "snippet": "Traffic HD meets EC1 Plus for low emissions and EN 71-3 toy safety...",
                          "citation": "Bona Traffic HD Product Page",
                          "source_type": "product",
                          "product_slug": "traffic-hd",
                          "cert_type": null,
                          "doc_title": "Bona Traffic HD",
                          "source_path": "architects/products/traffic-hd",
                          "product_url": "/architects/products/traffic-hd.html"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "missing q"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Vector search backend unavailable (Ollama down or DB missing).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/architects/api/ask": {
      "post": {
        "tags": [
          "ask"
        ],
        "summary": "Cited synthesis (sync)",
        "description": "Retrieves k chunks, reranks them, then calls Anthropic Haiku 4.5 to\nsynthesise a fully-cited Markdown answer. Every factual claim is tagged\nwith `[Source N]` referring to the 1-based citation index.\n\nFalls back to a degraded response (raw chunks) on Anthropic 429/5xx;\nsee the `degraded` and `note` fields on the response.\n\nRate limit: **10 requests/minute + 3/second per IP** in addition to the\ndefault 60/min.\n",
        "operationId": "askQuestion",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Minimal question",
                  "value": {
                    "question": "What is the VOC of Bona Traffic HD?"
                  }
                },
                "filtered": {
                  "summary": "Restricted to one product and source type",
                  "value": {
                    "question": "Which primer should I use on Blackbutt?",
                    "k": 8,
                    "product": "amber",
                    "type": "compatibility_matrix"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Synthesised answer.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                },
                "example": {
                  "ok": true,
                  "answer": "Bona Traffic HD has a VOC content of 130 g/L [Source 1]...",
                  "citations": [
                    {
                      "index": 1,
                      "label": "Bona Traffic HD TDS (2024), p.1",
                      "url": "/static/bona-pdfs/bona-traffic-hd-tds.pdf",
                      "page": 1,
                      "product": "traffic-hd"
                    }
                  ],
                  "sources_used": 1,
                  "elapsed_ms": 3421.7,
                  "model": "claude-haiku-4-5-20251001",
                  "cache_hit": false,
                  "degraded": false
                }
              }
            }
          },
          "400": {
            "description": "Empty or oversized question (max 1000 chars).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Retrieval layer failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Anthropic API returned a non-retryable error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/architects/api/ask/stream": {
      "post": {
        "tags": [
          "ask"
        ],
        "summary": "Cited synthesis (SSE stream)",
        "description": "Same synthesis pipeline as `/architects/api/ask` but streamed back as\nServer-Sent Events. Useful for time-to-first-token UX.\n\n**Event types** (all event data is JSON):\n  - `retrieval` - debug info: `{union_size, reranked_size, rewrite_queries}`\n  - `citations` - tentative citations: `{citations: [Citation]}`\n  - `delta`     - streamed token: `{text: string}`\n  - `done`      - terminal frame: `{elapsed_ms, sources_used, citations?, usage?, model?, degraded?, note?}`\n",
        "operationId": "askQuestionStream",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              },
              "example": {
                "question": "List every Bona product that holds EN 71-3.",
                "k": 8
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent Events stream.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              },
              "Content-Type": {
                "schema": {
                  "type": "string",
                  "const": "text/event-stream"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Raw SSE frames. Each frame is a pair of `event:` and `data:`\nlines separated by a blank line. Example:\n\n```\nevent: delta\ndata: {\"text\": \"Bona Traffic HD has a VOC of 130 g/L...\"}\n\nevent: done\ndata: {\"elapsed_ms\": 3421.7, \"sources_used\": 2, \"model\": \"claude-haiku-4-5-20251001\"}\n```\n"
                },
                "examples": {
                  "stream": {
                    "summary": "Two-frame stream",
                    "value": "event: retrieval\ndata: {\"union_size\": 12, \"reranked_size\": 8, \"rewrite_queries\": [\"VOC of Traffic HD\", \"Bona Traffic HD emissions\"]}\n\nevent: citations\ndata: {\"citations\": [{\"index\":1,\"label\":\"Bona Traffic HD TDS (2024)\",\"url\":\"/static/bona-pdfs/bona-traffic-hd-tds.pdf\",\"page\":1,\"product\":\"traffic-hd\"}]}\n\nevent: delta\ndata: {\"text\": \"Bona Traffic HD has a VOC content of 130 g/L [Source 1].\"}\n\nevent: done\ndata: {\"elapsed_ms\": 3421.7, \"sources_used\": 1, \"citations\": [{\"index\":1,\"label\":\"Bona Traffic HD TDS (2024)\",\"url\":\"/static/bona-pdfs/bona-traffic-hd-tds.pdf\",\"page\":1,\"product\":\"traffic-hd\"}], \"model\": \"claude-haiku-4-5-20251001\"}\n"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Empty or oversized question (max 1000 chars).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Retrieval failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/architects/api/ask/health": {
      "get": {
        "tags": [
          "ask",
          "health"
        ],
        "summary": "Synthesis layer liveness",
        "description": "Performs a 1-token Anthropic Messages API ping to confirm the synthesis layer is reachable and credentials are valid.",
        "operationId": "askHealth",
        "responses": {
          "200": {
            "description": "Synthesis layer reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskHealth"
                },
                "example": {
                  "ok": true,
                  "anthropic_reachable": true,
                  "latency_ms": 312.4,
                  "model": "claude-haiku-4-5-20251001",
                  "cache_entries": 17
                }
              }
            }
          },
          "503": {
            "description": "Synthesis layer unreachable (no API key, or Anthropic returned error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskHealth"
                },
                "example": {
                  "ok": false,
                  "anthropic_reachable": false,
                  "error": "ANTHROPIC_API_KEY not set"
                }
              }
            }
          }
        }
      }
    },
    "/architects/api/clause": {
      "post": {
        "tags": [
          "clause"
        ],
        "summary": "Generate a specification clause (NATSPEC / MasterFormat / NBS)",
        "description": "Generates a copy-paste-ready specification clause for a named Bona\nproduct. Cites every numeric value back to source PDFs. Can also list\nany attachments (TDS, SDS, EPD, certificates) that should ship with the\nspec package.\n\n**Note:** this endpoint is under active development. The response\nschema is stable but some optional fields (`masterformat_section`,\n`natspec_worksection`) may be `null` while the authoring pipeline is\ntuned.\n",
        "operationId": "generateClause",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClauseRequest"
              },
              "examples": {
                "natspec_au": {
                  "summary": "NATSPEC clause for AU",
                  "value": {
                    "product": "traffic-hd",
                    "substrate": "blackbutt",
                    "gloss": "matte",
                    "use_case": "commercial",
                    "region": "AU",
                    "format": "natspec",
                    "compliance_targets": [
                      "EC1-Plus",
                      "EN71-3"
                    ]
                  }
                },
                "masterformat_us": {
                  "summary": "MasterFormat section 09 64 29",
                  "value": {
                    "product": "traffic-hd",
                    "region": "US",
                    "format": "masterformat"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated clause.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClauseResponse"
                },
                "example": {
                  "ok": true,
                  "product": "traffic-hd",
                  "format": "natspec",
                  "clause_text": "09 64 29 TIMBER FLOOR COATING - BONA TRAFFIC HD\nApply Bona Traffic HD two-component waterborne polyurethane...\n",
                  "masterformat_section": null,
                  "natspec_worksection": "0654 TIMBER FLOORING",
                  "citations": [
                    {
                      "label": "Bona Traffic HD TDS (2024)",
                      "url": "/static/bona-pdfs/bona-traffic-hd-tds.pdf",
                      "page": 1
                    }
                  ],
                  "required_attachments": [
                    {
                      "filename": "bona-traffic-hd-tds.pdf",
                      "url": "/static/bona-pdfs/bona-traffic-hd-tds.pdf"
                    },
                    {
                      "filename": "bona-traffic-hd-sds.pdf",
                      "url": "/static/bona-pdfs/bona-traffic-hd-sds.pdf"
                    }
                  ],
                  "warnings": []
                }
              }
            }
          },
          "400": {
            "description": "Missing product slug or invalid parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Product slug not in corpus.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "Upstream synthesis layer failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque",
        "description": "Optional bearer token. Reserved for future elevated rate limits; the\ntoken is currently accepted but not enforced.\n"
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Max requests allowed in the current window (default 60/min).",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "XRateLimitRemaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer",
          "example": 59
        }
      },
      "XRateLimitReset": {
        "description": "Unix epoch (seconds) at which the current window resets.",
        "schema": {
          "type": "integer",
          "example": 1744994400
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/XRateLimitReset"
          },
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "example": 30
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "ok": false,
              "error": "rate limit exceeded"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Short machine-readable error token.",
            "example": "missing q"
          },
          "detail": {
            "type": "string",
            "description": "Optional human-readable extra context."
          }
        }
      },
      "SourceType": {
        "type": "string",
        "enum": [
          "product",
          "pdf",
          "cert",
          "faq",
          "compatibility_matrix"
        ],
        "description": "Kind of corpus chunk this result came from.\n  - `product` - per-product landing page body\n  - `pdf` - indexed TDS/SDS/EPD/brochure\n  - `cert` - third-party certificate metadata\n  - `faq` - generated FAQ answer\n  - `compatibility_matrix` - primer x species matrix entry\n"
      },
      "CertType": {
        "type": "string",
        "enum": [
          "as4586-slip",
          "din51130-slip",
          "dibt",
          "emicode",
          "en13501-fire",
          "en14904-sport",
          "en71-toy-safety",
          "epd",
          "greenguard",
          "iso14001"
        ]
      },
      "SearchFilters": {
        "type": "object",
        "description": "Echoed server-side representation of the applied filters. Keys may be\nabsent when not supplied.\n",
        "additionalProperties": false,
        "properties": {
          "product_slug": {
            "type": "string"
          },
          "source_type": {
            "$ref": "#/components/schemas/SourceType"
          },
          "cert_type": {
            "$ref": "#/components/schemas/CertType"
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "required": [
          "score",
          "snippet",
          "citation",
          "source_type"
        ],
        "properties": {
          "score": {
            "type": "number",
            "format": "float",
            "description": "Vector similarity score (0-1, higher is better).",
            "example": 0.891
          },
          "snippet": {
            "type": "string",
            "description": "Trimmed snippet of the chunk (max 320 chars)."
          },
          "citation": {
            "type": "string",
            "description": "Human-readable citation for this chunk.",
            "example": "Bona Traffic HD TDS (2024), p.1"
          },
          "source_type": {
            "$ref": "#/components/schemas/SourceType"
          },
          "product_slug": {
            "type": [
              "string",
              "null"
            ],
            "description": "Product slug when the chunk is product-scoped."
          },
          "cert_type": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CertType"
              },
              {
                "type": "null"
              }
            ]
          },
          "doc_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal corpus path (filename / page URL)."
          },
          "product_url": {
            "type": "string",
            "description": "Convenience deep-link into the product page.",
            "example": "/architects/products/traffic-hd.html"
          },
          "cert_url": {
            "type": "string",
            "description": "Convenience deep-link into the compliance page when source_type=cert.",
            "example": "/architects/compliance/ec1-plus.html"
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": [
          "ok",
          "query",
          "k",
          "filters",
          "elapsed_ms",
          "count",
          "results"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "query": {
            "type": "string"
          },
          "k": {
            "type": "integer",
            "minimum": 1,
            "maximum": 25
          },
          "filters": {
            "$ref": "#/components/schemas/SearchFilters"
          },
          "elapsed_ms": {
            "type": "number",
            "format": "float",
            "description": "Total server-side latency in milliseconds."
          },
          "timing": {
            "type": [
              "object",
              "null"
            ],
            "description": "Optional per-stage timing debug from the vector backend.",
            "additionalProperties": true
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          }
        }
      },
      "AskRequest": {
        "type": "object",
        "required": [
          "question"
        ],
        "properties": {
          "question": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000,
            "description": "The architect's question in plain English."
          },
          "k": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "default": 8,
            "description": "Number of chunks to retrieve and feed to the synthesiser."
          },
          "product": {
            "type": "string",
            "description": "Optional product-slug filter applied before retrieval.",
            "example": "traffic-hd"
          },
          "type": {
            "$ref": "#/components/schemas/SourceType"
          },
          "cert_type": {
            "$ref": "#/components/schemas/CertType"
          }
        }
      },
      "Citation": {
        "type": "object",
        "required": [
          "index",
          "label"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 1,
            "description": "1-based citation number that `[Source N]` references in the answer body."
          },
          "label": {
            "type": "string",
            "description": "Human-readable citation label, e.g. \"Bona Traffic HD TDS (2024)\"."
          },
          "url": {
            "type": "string",
            "format": "uri-reference",
            "description": "Optional deep-link back to the source document or page."
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "description": "Page number in the source PDF when applicable."
          },
          "product": {
            "type": "string",
            "description": "Product slug when the citation is product-scoped."
          }
        }
      },
      "Usage": {
        "type": "object",
        "description": "Anthropic Messages API usage block, passed through when available.",
        "additionalProperties": true,
        "properties": {
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "cache_read_input_tokens": {
            "type": "integer"
          },
          "cache_creation_input_tokens": {
            "type": "integer"
          }
        }
      },
      "RetrievalDebug": {
        "type": "object",
        "properties": {
          "rewrite_queries": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "union_size": {
            "type": "integer"
          },
          "reranked_size": {
            "type": "integer"
          },
          "retrieval_ms": {
            "type": "number",
            "format": "float"
          }
        }
      },
      "AskResponse": {
        "type": "object",
        "required": [
          "ok",
          "answer",
          "citations",
          "sources_used",
          "elapsed_ms",
          "cache_hit",
          "degraded"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "answer": {
            "type": "string",
            "description": "Markdown answer with inline `[Source N]` citations."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "sources_used": {
            "type": "integer",
            "minimum": 0,
            "description": "Count of distinct citations referenced in the answer body."
          },
          "elapsed_ms": {
            "type": "number",
            "format": "float"
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "Anthropic model identifier, or null when the response is degraded.",
            "example": "claude-haiku-4-5-20251001"
          },
          "cache_hit": {
            "type": "boolean",
            "description": "True when the answer was served from the in-process 1h TTL LRU."
          },
          "degraded": {
            "type": "boolean",
            "description": "True when the response is a raw-chunk fallback (Anthropic 429/5xx/timeout)."
          },
          "note": {
            "type": "string",
            "description": "Present on degraded responses to describe the fallback cause."
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "retrieval_debug": {
            "$ref": "#/components/schemas/RetrievalDebug"
          }
        }
      },
      "AskHealth": {
        "type": "object",
        "required": [
          "ok",
          "anthropic_reachable"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "anthropic_reachable": {
            "type": "boolean"
          },
          "latency_ms": {
            "type": "number",
            "format": "float"
          },
          "model": {
            "type": "string"
          },
          "cache_entries": {
            "type": "integer",
            "minimum": 0
          },
          "error": {
            "type": "string"
          }
        }
      },
      "ClauseRequest": {
        "type": "object",
        "required": [
          "product"
        ],
        "properties": {
          "product": {
            "type": "string",
            "description": "Product slug (required), e.g. `traffic-hd`, `mega-one`, `craft-oil-2k`.",
            "minLength": 1
          },
          "substrate": {
            "type": "string",
            "description": "Optional substrate / species slug, e.g. `blackbutt`, `spotted-gum`, `concrete`."
          },
          "gloss": {
            "type": "string",
            "description": "Optional gloss level, e.g. `matte`, `silk-matt`, `gloss`."
          },
          "use_case": {
            "type": "string",
            "description": "Optional building use, e.g. `residential`, `commercial`, `sport`."
          },
          "region": {
            "type": "string",
            "enum": [
              "AU",
              "UK",
              "US"
            ],
            "default": "AU",
            "description": "Spec flavour locale."
          },
          "format": {
            "type": "string",
            "enum": [
              "natspec",
              "masterformat",
              "nbs"
            ],
            "default": "natspec",
            "description": "Output spec format. `natspec` for AU, `masterformat` for US, `nbs` for UK."
          },
          "compliance_targets": {
            "type": "array",
            "description": "Certifications / standards the clause must cite, e.g. `EC1-Plus`, `EN71-3`, `AS4586`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ClauseAttachment": {
        "type": "object",
        "required": [
          "filename",
          "url"
        ],
        "properties": {
          "filename": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri-reference"
          }
        }
      },
      "ClauseResponse": {
        "type": "object",
        "required": [
          "ok",
          "product",
          "format",
          "clause_text",
          "citations",
          "required_attachments",
          "warnings"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "product": {
            "type": "string"
          },
          "format": {
            "type": "string",
            "enum": [
              "natspec",
              "masterformat",
              "nbs"
            ]
          },
          "clause_text": {
            "type": "string",
            "description": "The full clause body, ready to paste into a specification document."
          },
          "masterformat_section": {
            "type": [
              "string",
              "null"
            ],
            "description": "CSI MasterFormat section code when `format=masterformat`, e.g. `09 64 29`."
          },
          "natspec_worksection": {
            "type": [
              "string",
              "null"
            ],
            "description": "NATSPEC worksection code when `format=natspec`, e.g. `0654`."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "required_attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseAttachment"
            }
          },
          "warnings": {
            "type": "array",
            "description": "Non-fatal issues the spec writer should review (missing data, substitutions).",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
