api.wheelofheaven.world

The static JSON API — Zola-generated endpoints serving every curated artefact in the corpus.

≈ 8 min read 1413 words Updated 2026-08-29

On this page

The Wheel of Heaven API is a static JSON twin of the corpus. Every content page on www.wheelofheaven.world — wiki, timeline, articles, news, library, sources — has a deterministic JSON URL on api.wheelofheaven.world, plus surfaces (bibliography, glossary, translation provenance, schemas, controlled vocabularies) that don’t exist on www.

The mirror covers the corpus, not the whole site. Chrome and utility pages — /read/, /faq/, /map/, /gallery/, /listen/, /datasets/, the taxonomy and policy pages — have no twin, by design. See The twin link contract below.

For the catalogue of every endpoint, the response envelope, controlled vocabularies, and JSON Schemas, see API Reference.

Who this is for#

The API exists so machines — AI agents, third-party integrations, analytics, downstream sites — can read the same corpus the reading site presents to humans. It is also the entry point we deliberately expose to LLMs: every content page has a JSON twin, and a handful of /v1/context/* endpoints are curated as direct system-prompt material.

This is not a database in front of an application server. Every URL is a static file, pre-built at deploy time from data-content, data-library, and data-bibliography. No queries, no auth, no rate limits beyond Cloudflare’s edge.

Overview#

  • URL: https://api.wheelofheaven.world
  • Generator: Zola (with a Python prebuild step)
  • Hosting: Cloudflare Pages (migrated from GitHub Pages in May 2026)
  • Format: JSON, static, CC0-1.0
  • Versioning: strictly additive within /v1/. Breaking changes earn /v2/. Decision 14 of strategy-decisions.md locks v1 URLs forever.

Design principles#

The full shape is documented in the org-level strategy doc strategy-api.md. The principles in one line each:

  1. Static, file-backed, pre-built. No query engine, no runtime logic. Every URL is a deterministic file.
  2. Resource-noun naming. URLs name what consumers want (/books/, /wiki/, /sources/), not the storage warehouse.
  3. One way to do everything. Each fact has one canonical URL.
  4. URL permanence for v1. Shipped paths never break.
  5. Same envelope everywhere. { apiVersion, kind, metadata, data, links } for every endpoint.
  6. Multilingual by path prefix. English at /v1/..., others at /v1/{lang}/....
  7. Self-describing. A consumer with just the root URL can walk the entire surface — manifest, schemas, enums, sitemap, llms.txt.
  8. AI-first metadata, not AI-only. Same envelope serves humans, scripts, and LLMs; /v1/context/* adds curated narrative summaries for direct LLM ingestion.

How it’s built#

The API repo is wheelofheaven/api.wheelofheaven.world. Build steps:

  1. python3 scripts/prebuild.py — reads data/content/ (wiki, timeline, articles, news, tradition hubs), data/library/ (the 100+ books), and data/bibliography/ (67 source records). Writes per-section index data to data/extracted/ and one Zola content page per entry under content/v1/. For non-default languages it walks data/content/{lang}/ and emits to content/v1/{lang}/. The generated content files are gitignored.

  2. zola build — Zola renders every content page through its bound template = "v1-*.json" template, producing one index.html per endpoint containing JSON.

  3. bash scripts/postbuild.sh — writes _headers for proper Content-Type: application/json + CORS + cache TTLs, and writes _redirects so directory URLs are canonical and explicit-extension URLs (/v1/.../index.json) rewrite to the directory form (200, no actual file mirror).

The pipeline runs on every push to main and on the Cloudflare Pages build hook.

Deployment file-count budget#

The API has the largest page count of any Wheel of Heaven site and is the one closest to Cloudflare Pages’ 20,000-file deployment cap. Current builds sit at ~3,700 files (well under), but two design choices keep it there:

  1. The postbuild does not mirror every index.html as a sibling index.json_redirects does the same job via a rewrite rule without doubling the file count.
  2. Per-language library mirrors (/v1/{lang}/library/...) are filtered to books whose catalog availableLangs includes {lang}. Without that filter, 9 languages × ~2,000 English pages = ~18,000 extra files, which crosses the cap.

Before merging changes that add a new content surface or mirror, run mise run build && find public -type f | wc -l locally. If the result is above ~15,000, plan the additional surface carefully. See Hosting and Caching → CF Pages 20,000-file deployment cap for the failure mode and the working patterns.

Data sources#

Source repoWhatAPI surface
data-contentwiki, timeline, articles, news, tradition hubs, i18n glossary/v1/wiki/, /v1/timeline/, /v1/articles/, /v1/news/, /v1/sources/traditions/, /v1/glossary/
data-librarycatalog + 100+ books with chapter/verse JSON/v1/library/books/, /v1/library/traditions/, /v1/translations/ (for the -woh family)
data-bibliography67 structured source records/v1/sources/

The same submodules feed www. The two sites are independent Zola builds reading the same canonical data.

URL conventions#

  • /v1/{kind}/ returns the listing for that kind.
  • /v1/{kind}/{slug}/ returns the individual item.
  • /v1/{lang}/{kind}/[{slug}/] returns the same in another language.
  • Every directory URL also responds at /v1/{kind}/index.json and /v1/{kind}/{slug}/index.json (historical canonical form).

Caching#

SurfaceCache TTL
/v1/schema/*, /v1/enums/*, /v1/context/*24 hours
/v1/wiki/*, /v1/timeline/*, /v1/articles/*, /v1/library/*, etc.1 hour
robots.txt, sitemap.xml, llms.txtdefault (1 hour)

Cloudflare purges the entire zone on each successful deploy.

Discoverability#

  • Every consumer endpoint links back to /v1/ (manifest), /v1/schema/{kind}/ (its schema), and the relevant /v1/enums/.
  • /llms.txt (API-side) is a short manifest pointing AI agents at /v1/context/* and /v1/.
  • Content pages on www carry a <link rel="alternate" type="application/json"> pointing to their API twin — see the contract below for exactly which.

Every mirrored www page advertises its JSON counterpart in <head>:

<link rel="alternate" type="application/json"
      title="Wheel of Heaven - JSON API twin"
      href="https://api.wheelofheaven.world/v1/wiki/elohim/">

This is the highest-volume agent-facing signal the project emits. An agent that follows a citation to a page URL finds the cheap representation from there, without needing to know the API exists.

Why it matters — measured on /wiki/elohim/:

Sizevs page
Rendered HTML350,364 B1.0×
API body_html55,690 B6.3× smaller
Plain-text floor48,519 B7.2× smaller

The saving is in dropping page chrome — nav, footer, JSON-LD, inline SVG — not in the body format. Converting that body from HTML to Markdown would add only 1.15× on top. This is the number to reach for whenever “should we serve Markdown?” comes up: the twin link already banks ~98% of the available reduction, at the canonical URL, with no new infrastructure.

Which pages emit it#

Emitted for the site root, and for any path whose section — after any language prefix is stripped — is wiki, timeline, articles, news, library or sources. Language variants are included (/de/wiki/elohim//v1/de/wiki/elohim/).

Two pages have a counterpart under a different path and are mapped explicitly:

www pageTwin
/about//v1/context/
/method//v1/context/method/

Those two /v1/context/* endpoints are English-only/v1/de/context/ is a 404 — so the override matches the full path, not the language-stripped section. /de/about/ therefore emits nothing rather than advertising an English twin for a translated page.

Everything else emits no link: language homepages (/v1/{lang}/ is render = false by design), and all chrome, utility, taxonomy and policy pages.

The invariant#

Never advertise a twin that doesn’t resolve. A rel="alternate" pointing at a 404 is worse than no link — it burns a request and teaches agents to distrust the signal. Before today’s audit the link was emitted unconditionally and ~266 pages advertised twins that 404’d.

So: adding a new www section means either adding its API mirror or leaving it out of the allow-list in themes/bifrost/templates/partials/seo.html. Changing the mirror means updating that list in the same change.

Template hazards#

Two Tera traps bit this tag, both live for a long time before being caught:

  • default() in output position double-escapes. Slashes rendered as &#x2F;, giving .../v1&#x2F;wiki&#x2F;elohim&#x2F;. Conformant HTML parsers decode it; naive scrapers — the audience the link exists for — read a broken URL. Hoist into a {% set %}, then emit with | safe.
  • current_path has no leading slash on some language homepages (ja, ko, ru, zh, zh-Hant), which produced .../v1ja/. Don’t depend on it: strip any leading slash and re-add exactly one.

Edit this page on GitHub