{"openapi":"3.1.0","info":{"title":"HANK Docs Public Content API","version":"1.0.0","description":"The PUBLIC, unauthenticated content API for the HANK documentation platform — the machine twins an integrator or AI agent reads: a discovery tree, hybrid search, section retrieval, the changelog feed, the glossary, and a grounded Ask-AI endpoint. NO authentication is required or honored on any surface (no bearer, no cookie) — this describes the read/ask contract, not the bearer-gated `/api/v1` management API (which has its own spec at `/api/v1/openapi.json`). Reads are CORS-`*` and safe to call cross-origin from a browser. The polled GET reads set a strong content `ETag` and honor `If-None-Match` (→ `304`) for cheap re-polling. Failures are the bare `{ \"error\": string }` body (no machine `code`). `POST /api/ask` is rate-limited per source IP (a 429 with `Retry-After`); the other surfaces are unmetered reads."},"servers":[{"url":"https://docs.hank.ai/api","description":"The public content API."}],"tags":[{"name":"discovery","description":"The slug discovery tree."},{"name":"search","description":"Hybrid keyword + semantic search."},{"name":"content","description":"Section-anchored content retrieval."},{"name":"changelog","description":"Public release-note feed."},{"name":"glossary","description":"The reader-facing jargon dictionary."},{"name":"ask","description":"Grounded Ask-AI over the public docs."}],"paths":{"/structure":{"get":{"tags":["discovery"],"summary":"The public discovery tree — products → manuals → topics (slugs + titles only) — so an agent can LEARN real slugs before fetching a page. Parameterless; internal/draft topics and non-public products never appear.","parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"Conditional GET — echo a prior response `ETag`; an unchanged payload answers `304 Not Modified` (no body) so a re-polling crawler/agent skips the re-download.","schema":{"type":"string"}}],"responses":{"200":{"description":"The public discovery tree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StructureResponse"},"example":{"products":[{"slug":"hank-codes","name":"HANK CODES","manuals":[{"slug":"getting-started","title":"Getting started","topics":[{"slug":"quickstart","title":"Quickstart"}]}]}]}}},"headers":{"ETag":{"$ref":"#/components/headers/ETag"}}},"304":{"description":"Not modified — the client `If-None-Match` matched the current `ETag`; no body."}}}},"/changelog":{"get":{"tags":["changelog"],"summary":"Public changelog feed. `?product=<slug>` PRESENT → that product's public+published entries (full body + tags), newest first. ABSENT → the GLOBAL cross-product feed, each entry carrying its owning product slug/name + deep-link anchor.","parameters":[{"name":"product","in":"query","required":false,"description":"Optional canonical product slug. ABSENT → the global cross-product feed; a MALFORMED slug is a 400; a valid-but-unknown / non-public slug returns an empty `{ entries: [] }` (never confirms a non-public product exists).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, a positive integer bounded to [1, 100] (default 20). A bad value is a 400.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"If-None-Match","in":"header","required":false,"description":"Conditional GET — echo a prior response `ETag`; an unchanged payload answers `304 Not Modified` (no body) so a re-polling crawler/agent skips the re-download.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of public changelog entries (per-product or the global cross-product feed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangelogResponse"},"example":{"entries":[{"id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","productSlug":"hank-codes","productName":"HANK CODES","version":"2.1.0","entryDate":"2026-07-01","bodyMdx":"- Added batch claim submission\n- Fixed a 429 edge case on burst submits","anchor":"2-1-0"}]}}},"headers":{"ETag":{"$ref":"#/components/headers/ETag"}}},"304":{"description":"Not modified — the client `If-None-Match` matched the current `ETag`; no body."},"400":{"description":"Malformed product slug or bad limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/search":{"get":{"tags":["search"],"summary":"Hybrid search over the public corpus — keyword FTS/trgm fused with semantic retrieval (RRF). Degrades gracefully: any search-path failure returns a 200 with an empty result set and `degraded: true`, NEVER a 500. A bad query is the only non-200 (a 400).","parameters":[{"name":"q","in":"query","required":true,"description":"The search query (required, non-empty).","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"description":"Max hits, a positive integer bounded to [1, 50] (default 20).","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"offset","in":"query","required":false,"description":"Page START offset, a non-negative integer (default 0). Page it against `total`/`hasMore`.","schema":{"type":"integer","minimum":0}},{"name":"product","in":"query","required":false,"description":"Optional product slug — restrict results to a single product.","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"Optional corpus restriction — `topic` (guide pages), `changelog` (release notes), `reference` (developer reference: dev-portal pages + API-spec operation summaries), or `glossary` (glossary terms).","schema":{"type":"string","enum":["topic","changelog","reference","glossary"]}},{"name":"sort","in":"query","required":false,"description":"Result ordering (default `relevance`, the RRF fusion). `newest`/`oldest` select a KEYWORD-ONLY date-ordered path (semantic-only matches do not appear under those).","schema":{"type":"string","enum":["relevance","newest","oldest"]}}],"responses":{"200":{"description":"The ranked hits + pagination (or an empty, `degraded` set).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"},"example":{"results":[{"kind":"topic","topicId":"9f8e7d6c-5b4a-4938-8271-6a5b4c3d2e1f","topicSlug":"quickstart","manualSlug":"getting-started","productSlug":"hank-codes","title":"Quickstart","excerptHtml":"How to <mark>authenticate</mark> your first request.","rank":0.0312,"matchedVia":"both"}],"total":1,"hasMore":false}}}},"400":{"description":"Invalid query (e.g. missing `q`, or a bad `limit`/`offset`/`kind`/`sort`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/section":{"get":{"tags":["content"],"summary":"Fetch ONE anchored section of a published topic (reconstructed from the derived chunk index) instead of the whole page — the slug triple plus an optional heading `anchor`.","parameters":[{"name":"product","in":"query","required":true,"description":"Canonical product slug.","schema":{"type":"string"}},{"name":"manual","in":"query","required":true,"description":"Canonical manual slug.","schema":{"type":"string"}},{"name":"topic","in":"query","required":true,"description":"Canonical topic slug.","schema":{"type":"string"}},{"name":"anchor","in":"query","required":false,"description":"GitHub-style heading slug of the section (`[a-z0-9-]*`, bounded). Absent / empty → the lead/title section. A malformed anchor is a 400.","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"Conditional GET — echo a prior response `ETag`; an unchanged payload answers `304 Not Modified` (no body) so a re-polling crawler/agent skips the re-download.","schema":{"type":"string"}}],"responses":{"200":{"description":"The reconstructed section + its deep-link href.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectionResponse"},"example":{"section":{"productSlug":"hank-codes","manualSlug":"getting-started","topicSlug":"quickstart","topicTitle":"Quickstart","anchor":"authentication","heading":"Authentication","content":"Send your Hank-issued bearer token in the Authorization header on every request.","href":"/hank-codes/getting-started/quickstart#authentication"}}}},"headers":{"ETag":{"$ref":"#/components/headers/ETag"}}},"304":{"description":"Not modified — the client `If-None-Match` matched the current `ETag`; no body."},"400":{"description":"Malformed slug, malformed anchor, or an unexpected query key (strict).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such public+published section (unknown/internal/draft/non-public topic, or an unknown anchor). Identical to \"absent\" — never confirms an internal/draft resource exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/related":{"get":{"tags":["content"],"summary":"The topics most semantically similar to a published topic — the same \"Related\" list the guide page renders — so a consumer can traverse the corpus by relatedness. Slug triple + optional `limit`.","parameters":[{"name":"product","in":"query","required":true,"description":"Canonical product slug.","schema":{"type":"string"}},{"name":"manual","in":"query","required":true,"description":"Canonical manual slug.","schema":{"type":"string"}},{"name":"topic","in":"query","required":true,"description":"Canonical topic slug.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max related topics (clamped to [1, 12]; default 5). A bad value is a 400.","schema":{"type":"integer","minimum":1,"maximum":12}},{"name":"If-None-Match","in":"header","required":false,"description":"Conditional GET — echo a prior response `ETag`; an unchanged payload answers `304 Not Modified` (no body) so a re-polling crawler/agent skips the re-download.","schema":{"type":"string"}}],"responses":{"200":{"description":"The related topics, most-similar first (a possibly-empty list — an unknown/internal/draft topic returns an empty list, never a 404, so it never confirms a non-public topic exists).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelatedResponse"},"example":{"related":[{"productSlug":"hank-codes","manualSlug":"getting-started","topicSlug":"authentication","title":"Authentication","score":0.87}]}}},"headers":{"ETag":{"$ref":"#/components/headers/ETag"}}},"304":{"description":"Not modified — the client `If-None-Match` matched the current `ETag`; no body."},"400":{"description":"Malformed slug, a non-positive `limit`, or an unexpected query key (strict).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/glossary":{"get":{"tags":["glossary"],"summary":"The reader-facing jargon dictionary as JSON. `?product=<slug>` PRESENT → global terms PLUS that product's scoped vocabulary. ABSENT → global terms only. Never 400s (any slug is a candidate; a bad/huge limit is clamped).","parameters":[{"name":"product","in":"query","required":false,"description":"Optional product slug — add that product's scoped terms to the global set. An empty or unmatched slug collapses to the global-only set (never an error).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size; clamped to [1, 500] (default 200). A bad/huge value is clamped, never a 400.","schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"If-None-Match","in":"header","required":false,"description":"Conditional GET — echo a prior response `ETag`; an unchanged payload answers `304 Not Modified` (no body) so a re-polling crawler/agent skips the re-download.","schema":{"type":"string"}}],"responses":{"200":{"description":"The public glossary terms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlossaryResponse"},"example":{"terms":[{"term":"RCM","slug":"rcm","definition":"Revenue cycle management — the financial process a provider uses to track claims.","productSlug":null,"deepLink":"https://docs.example.com/glossary#rcm"}]}}},"headers":{"ETag":{"$ref":"#/components/headers/ETag"}}},"304":{"description":"Not modified — the client `If-None-Match` matched the current `ETag`; no body."}}}},"/ask":{"post":{"tags":["ask"],"summary":"Ask a natural-language question, grounded in the public docs. Returns a grounded `answer` with `citations`, or a graceful refusal (`refused: true`) — always a 200, never a 5xx. The optional `product` slug NARROWS retrieval to one product, so every citation comes from that product's docs (an unmatched slug behaves like an uncovered question: a grounded refusal, not an error). Rate-limited per source IP; over the cap is a 429 with a `Retry-After`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"},"example":{"question":"How do I authenticate against the REST API?","product":"hank-codes"}}}},"responses":{"200":{"description":"The grounded answer + citations (or a graceful refusal).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"},"example":{"answer":"Send your Hank-issued bearer token in the `Authorization: Bearer <token>` header on every request to the `/api/v1` endpoints.","citations":[{"kind":"topic","title":"Quickstart","productSlug":"hank-codes","manualSlug":"getting-started","topicSlug":"quickstart","anchor":"authentication","snippet":"Send your Hank-issued bearer token in the Authorization header…"}],"refused":false}}}},"400":{"description":"Invalid JSON body, a `question` that is empty / over 2000 chars / with an extra key, or a non-slug `product` value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-IP rate limit exceeded; retry after the `Retry-After` interval.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"additionalProperties":false,"properties":{"error":{"type":"string","description":"Human-readable failure reason."}}},"AskRequest":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":2000},"product":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"}},"required":["question"],"additionalProperties":false},"StructureResponse":{"type":"object","properties":{"products":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"manuals":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"topics":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"}},"required":["slug","title"],"additionalProperties":false}}},"required":["slug","title","topics"],"additionalProperties":false}}},"required":["slug","name","manuals"],"additionalProperties":false}}},"required":["products"],"additionalProperties":false},"ChangelogResponse":{"type":"object","properties":{"entries":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"entryDate":{"type":"string"},"version":{"type":"string"},"title":{"type":"string"},"bodyMdx":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}},"required":["id","entryDate","version","title","bodyMdx","tags"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"productSlug":{"type":"string"},"productName":{"type":"string"},"version":{"type":"string"},"entryDate":{"type":"string"},"bodyMdx":{"type":"string"},"anchor":{"type":"string"}},"required":["id","productSlug","productName","version","entryDate","bodyMdx","anchor"],"additionalProperties":false}]}}},"required":["entries"],"additionalProperties":false},"SearchResponse":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["topic","changelog","reference","glossary"]},"topicId":{"type":"string"},"topicSlug":{"type":"string"},"manualSlug":{"type":"string"},"productSlug":{"type":"string"},"title":{"type":"string"},"excerptHtml":{"type":"string"},"rank":{"type":"number"},"anchor":{"type":"string"},"matchedVia":{"type":"string","enum":["keyword","semantic","both"]},"entryDate":{"type":"string"},"version":{"type":"string"},"changelogAnchor":{"type":"string"},"referencePath":{"type":"string"},"glossaryPath":{"type":"string"}},"required":["kind","topicId","topicSlug","manualSlug","productSlug","title","excerptHtml","rank"],"additionalProperties":false}},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hasMore":{"type":"boolean"},"degraded":{"type":"boolean"}},"required":["results","total","hasMore"],"additionalProperties":false},"SectionResponse":{"type":"object","properties":{"section":{"type":"object","properties":{"productSlug":{"type":"string"},"manualSlug":{"type":"string"},"topicSlug":{"type":"string"},"topicTitle":{"type":"string"},"anchor":{"type":"string"},"heading":{"type":"string"},"content":{"type":"string"},"href":{"type":"string"}},"required":["productSlug","manualSlug","topicSlug","topicTitle","anchor","heading","content","href"],"additionalProperties":false}},"required":["section"],"additionalProperties":false},"RelatedResponse":{"type":"object","properties":{"related":{"type":"array","items":{"type":"object","properties":{"productSlug":{"type":"string"},"manualSlug":{"type":"string"},"topicSlug":{"type":"string"},"title":{"type":"string"},"score":{"type":"number"}},"required":["productSlug","manualSlug","topicSlug","title","score"],"additionalProperties":false}}},"required":["related"],"additionalProperties":false},"GlossaryResponse":{"type":"object","properties":{"terms":{"type":"array","items":{"type":"object","properties":{"term":{"type":"string"},"slug":{"type":"string"},"definition":{"type":"string"},"productSlug":{"anyOf":[{"type":"string"},{"type":"null"}]},"deepLink":{"type":"string"}},"required":["term","slug","definition","productSlug","deepLink"],"additionalProperties":false}}},"required":["terms"],"additionalProperties":false},"AskResponse":{"type":"object","properties":{"answer":{"type":"string"},"citations":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["topic","changelog","reference"]},"title":{"type":"string"},"productSlug":{"type":"string"},"manualSlug":{"type":"string"},"topicSlug":{"type":"string"},"anchor":{"type":"string"},"snippet":{"type":"string"},"referencePath":{"type":"string"}},"required":["kind","title","productSlug","manualSlug","topicSlug","anchor","snippet"],"additionalProperties":false}},"refused":{"type":"boolean"},"refusalReason":{"type":"string","enum":["no_context","provider_failure"]}},"required":["answer","citations","refused"],"additionalProperties":false}},"headers":{"ETag":{"description":"Strong content-derived ETag; echo it in a later `If-None-Match` for a cheap 304.","schema":{"type":"string"}}}}}