Contract and OpenAPI
Page actions
- View as Markdown (opens in a new tab)
- Ask ChatGPT (opens in a new tab)
- Ask Claude (opens in a new tab)
ChatGPT and Claude are third-party services. The link gives them this page's public address and a short instruction. This site records nothing.
Preview: the published page may be older or missing.
contract/openapi.json is generated from real responses and checked on every pull request; the golden vectors beside it pin what every route returns. How both are made, enforced and changed.
https://docs.gurbanisoul.com/api/contract-and-openapi/
The API’s shape is not written by hand. tools/gen_openapi.py declares each route once (tag,
a one-sentence description, parameters, limits, sample requests, plus a short verb-first
summary in SUMMARIES — “Search the Granth” — that the reference’s sidebar and headings show)
and then infers the response schemas from real
responses of the in-process API on the pinned database, so the spec cannot describe a field the
server does not return. The result, contract/openapi.json, drives the API reference
on this site and the routes page.
Two tables that must agree
The dispatcher (webapp/serve.py:ROUTES) says which handler and which bounded context serve a
route; the generator (tools/gen_openapi.py:ROUTES) says what a route takes and means. A unit
test fails if a route is on one side and not the other, and tools/gen_route_table.py --check
fails the docs build if the routes page was not regenerated after either changed.
# The description is the operation's full sentence; its short sidebar/heading label is in SUMMARIES.LENS = [ q("author", STR, "Lens: only verses by this author."), q("raag", STR, "Lens: only verses in this raag."), q("raags", STR, "Lens: only verses in these raags (comma-separated; the \"This hour\" lens)."), q("rahao", {"type": "string", "enum": ["1", "true"]}, "Lens: refrains (Rahao lines) only."), q("min_themes", dict(INT, minimum=0, maximum=8, default=0), "Lens: verses carrying at least this many themes."), q("ang_min", dict(INT, minimum=1, maximum=1430, default=1), "Lens: first Ang."), q("ang_max", dict(INT, minimum=1, maximum=1430, default=1430), "Lens: last Ang."),]
ROUTES = { "/api/meta": ("Meta", "Build identity and corpus statistics (version, commit, counts).", [], [("/api/meta", {})], []), "/api/health": ("Meta", "Integrity self-test: line/Ang counts, FTS, verbatim Mool Mantar, ੴ count, live verify, bani registry.", [], [("/api/health", {})], []), "/api/search": ("Search", "Search the Granth through the ranked waterfall (exact FTS, seeker lexicon, variants, English, passage, fold).", [ q("q", {"type": "string", "maxLength": 300}, "Query in Gurmukhi, romanised Gurmukhi or English.", True), q("mode", {"type": "string", "enum": ["auto", "gurmukhi", "roman", "english", "first", "theme"], "default": "auto"}, "Search mode."), q("limit", dict(INT, minimum=0, maximum=200, default=50), "Page size (clamped)."),Read from the source at build time — view these lines on GitHub at c3d9360.
Source: tools/gen_openapi.py — the head of the declarations table, read at build time.
How the spec is checked
python3 tools/gen_openapi.py # regenerate contract/openapi.json (needs db/sggs.sqlite)python3 tools/gen_openapi.py --check # what CI runs: regenerate in memory, exit 1 on any differenceweb-ci · python runs the check on every pull request, and asserts the spec covers every
dispatcher route. Response schemas are inferred with types merged across the sample requests, so a
field that is sometimes null is typed as nullable rather than guessed.
The golden vectors beside it
contract/ also holds the golden vectors — recorded outputs of the real functions — and
_meta.json with their counts and the db_sha256 they were built against. They pin values,
where the OpenAPI spec pins shape:
| File | What it fixes |
|---|---|
openapi.json | every route’s parameters and response schema |
golden_search.ndjson, golden_verify.ndjson | what search and verification return for recorded inputs |
golden_reader.ndjson, golden_timing.ndjson, golden_banis.ndjson, golden_analytics.ndjson | the reader, knowledge, Nitnem and insight projections |
golden_roman_norm.ndjson, golden_difflib.ndjson | the two pure functions the iOS port must reproduce byte for byte |
make contract regenerates the vectors and fails on drift; tools/contract_http.py replays them
over HTTP against a running API, which every deploy does before promoting. Details on
Harnesses and golden vectors.
The API contract version
API_CONTRACT_VERSION in tools/gen_openapi.py is the spec’s own version, bumped only for an API
change; a breaking change gets a new major and a new path prefix. The product version
(APP_VERSION, shown by /api/meta and /api/health) is a separate number shared by web, API and
app — see One version number.
Changing the API honestly
- Add or change the handler in the bounded context module and its entry in
serve.py:ROUTES. - Declare it in
tools/gen_openapi.py:ROUTESwith parameters, limits and at least one sample request, and give it a short label inSUMMARIES(at most 32 characters; a test holds both tables to the same routes); regenerate the spec. - Regenerate the golden vectors (
make contract) and review the diff line by line. - Regenerate the routes page (
python3 tools/gen_route_table.py) and, if the route belongs to a context that runs as its own function on staging, the gateway (tools/gen_gateway.py). - Ship through the normal pipeline; the deploy replays the contract through the gateway before promoting. The app re-vendors the contract at the next release tag.
Sri Guru Granth Sahib Ji — Knowledge Base wiki. Generated from commit c3d9360. Updated 11 October 2026. Verified against d2f2e41 on 2026-09-27. This copy may be out of date; the page is at https://docs.gurbanisoul.com/api/contract-and-openapi/