{"openapi":"3.0.3","info":{"title":"Almanac","version":"0.2.0","description":"Patiently gathered reference data about places and events, served at whatever speed you want. The first collection is `places`: a polite, continuously refreshing mirror of Wikidata's place data. Every `/v1/places/*` route requires an API key (`Authorization: Bearer <key>`); keys are issued by hand, not self-serve.\n","contact":{"url":"https://almanac.fluxoft.com"}},"security":[{"bearerAuth":[]}],"paths":{"/":{"get":{"security":[],"summary":"The \"what is this?\" landing page","description":"A static, human-readable page describing the service. Unauthenticated, like /health and /v1/openapi.json.\n","responses":{"200":{"description":"The landing page.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/health":{"get":{"security":[],"summary":"Liveness and readiness","description":"Answers ok only when the database answers.","responses":{"200":{"description":"Service and database are up.","content":{"application/json":{"schema":{"type":"object","required":["status","migrationsApplied"],"properties":{"status":{"type":"string","enum":["ok"]},"migrationsApplied":{"type":"integer"}}}}}},"503":{"description":"Database unreachable.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["unavailable"]}}}}}}}}},"/v1/openapi.json":{"get":{"security":[],"summary":"This document","responses":{"200":{"description":"The OpenAPI specification, as JSON."}}}},"/v1/places/changes":{"get":{"summary":"The change feed","description":"Items whose change_seq exceeds `since`, ascending, raw blob included. Cold start is this endpoint consumed from `since=0` -- there is no separate bulk path. An empty page echoes the caller's own cursor back (a stable idle poll) rather than leaving it undefined.\n","parameters":[{"name":"since","in":"query","description":"An opaque cursor (a stringified change_seq in practice -- treat it as opaque; its representation may change). Defaults to \"0\".\n","schema":{"type":"string","default":"0"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"A page of the feed.","content":{"application/json":{"schema":{"type":"object","required":["items","next_cursor","has_more"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PlaceItem"}},"next_cursor":{"type":"string"},"has_more":{"type":"boolean"}}}}}},"400":{"description":"since was not a non-negative integer string."},"401":{"description":"Missing, malformed, unknown, or inactive API key."},"429":{"description":"Rate limit or daily quota exceeded. Carries a Retry-After header."}}}},"/v1/places/{qid}":{"get":{"summary":"A single item, with fetch-through on cache miss","description":"Cache hit: the full record, same shape as a feed row. Cache miss: fetches through to Wikidata, politely, stores the result (it enters the feed), and serves it. Only well-formed Q-ids (^Q\\d+$) attempt a fetch; anything else is a plain 404.\n","parameters":[{"name":"qid","in":"path","required":true,"schema":{"type":"string","pattern":"^Q\\d+$"}}],"responses":{"200":{"description":"The item, from cache or freshly fetched through.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceItem"}}}},"401":{"description":"Missing, malformed, unknown, or inactive API key."},"404":{"description":"Malformed qid, or the source reports this item does not exist (status=absent).\n"},"429":{"description":"Rate limit or daily quota exceeded. Carries a Retry-After header."},"503":{"description":"The fetch-through attempt failed transiently. Never a fabricated 404."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"almnc_<key>. Issued by hand (api/scripts/issue-key.ts); no self-serve signup.\n"}},"schemas":{"PlaceItem":{"type":"object","required":["qid","status","redirect_target_qid","lastrevid","change_seq","fetched_at","changed_at","entity"],"properties":{"qid":{"type":"string"},"status":{"type":"string","enum":["live","absent","redirect"]},"redirect_target_qid":{"type":"string","nullable":true},"lastrevid":{"type":"integer","nullable":true},"change_seq":{"type":"integer"},"fetched_at":{"type":"string","format":"date-time"},"changed_at":{"type":"string","format":"date-time"},"entity":{"type":"object","nullable":true,"description":"The whole wbgetentities entity, untrimmed."}}}}}}