api.wheelofheaven.world
The static JSON API — Zola-generated endpoints serving every curated artefact in the corpus.
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 ofstrategy-decisions.mdlocks 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:
- Static, file-backed, pre-built. No query engine, no runtime logic. Every URL is a deterministic file.
- Resource-noun naming. URLs name what consumers want (
/books/,/wiki/,/sources/), not the storage warehouse. - One way to do everything. Each fact has one canonical URL.
- URL permanence for v1. Shipped paths never break.
- Same envelope everywhere.
{ apiVersion, kind, metadata, data, links }for every endpoint. - Multilingual by path prefix. English at
/v1/..., others at/v1/{lang}/.... - Self-describing. A consumer with just the root URL can walk the entire surface — manifest, schemas, enums, sitemap, llms.txt.
- 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:
python3 scripts/prebuild.py— readsdata/content/(wiki, timeline, articles, news, tradition hubs),data/library/(the 100+ books), anddata/bibliography/(67 source records). Writes per-section index data todata/extracted/and one Zola content page per entry undercontent/v1/. For non-default languages it walksdata/content/{lang}/and emits tocontent/v1/{lang}/. The generated content files are gitignored.zola build— Zola renders every content page through its boundtemplate = "v1-*.json"template, producing oneindex.htmlper endpoint containing JSON.bash scripts/postbuild.sh— writes_headersfor properContent-Type: application/json+ CORS + cache TTLs, and writes_redirectsso 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:
- The postbuild does not mirror every
index.htmlas a siblingindex.json—_redirectsdoes the same job via a rewrite rule without doubling the file count. - Per-language library mirrors (
/v1/{lang}/library/...) are filtered to books whose catalogavailableLangsincludes{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 repo | What | API surface |
|---|---|---|
data-content | wiki, timeline, articles, news, tradition hubs, i18n glossary | /v1/wiki/, /v1/timeline/, /v1/articles/, /v1/news/, /v1/sources/traditions/, /v1/glossary/ |
data-library | catalog + 100+ books with chapter/verse JSON | /v1/library/books/, /v1/library/traditions/, /v1/translations/ (for the -woh family) |
data-bibliography | 67 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.jsonand/v1/{kind}/{slug}/index.json(historical canonical form).
Caching#
| Surface | Cache 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.txt | default (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.
The twin link contract#
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/:
| Size | vs page | |
|---|---|---|
| Rendered HTML | 350,364 B | 1.0× |
API body_html | 55,690 B | 6.3× smaller |
| Plain-text floor | 48,519 B | 7.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 page | Twin |
|---|---|
/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/, giving.../v1/wiki/elohim/. 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_pathhas 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.
Related#
- API Reference — tabular endpoint catalogue, envelope spec, enum and schema reference.
- Hosting and Caching — how every site is cached.
- CI & Deploy — the build and deploy chain.