UC Wiki
Public API · docs

Read the archive, programmatically

Three corpora — 322 books, 19,245 speeches, and a growing testimony collection — exposed over a small, uniform JSON API. Same filter contract everywhere (title, author, date, word-count, tags, format), open CORS, rate-limited per IP. Free, no auth. See the legacy /api/v1 landing for an older summary; this page is the current reference.

Rate limits + CORS

Per-IP, fixed window — values come from lib/rate-limit.ts:

  • content bucket — 60 req per 60s. Covers /api/v1/books*, /api/v1/speeches*, /api/v1/testimonies*, /api/v1/everything.
  • search bucket — 10 req per 60s. Covers /api/v1/search (the body-indexed corpus search).

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. On overrun you get a 429 with Retry-After. CORS is open (Access-Control-Allow-Origin: *) so you can call from a browser too.

Several list endpoints accept ?format=jsonl (one JSON object per line, no trailing newline — easy to stream-parse) or ?format=csv (RFC 4180, quotes doubled). The CSV/JSONL responses are the row slice only — no pagination envelope, since CSV consumers expect rows.

Books

The 322-volume Unification source library. Title/author filters, word-count ranges, sortable, CSV/JSONL export.

GET/api/v1/booksbucket: content · cache: 300s

Paginated list of every book with the shared common-filter contract. Word counts are only computed when filtered or sorted by length (see q below); the field is null otherwise.

Query params

q
string
Substring across title + author. Case-insensitive.
e.g. q=peace
q_title
string
Title-only substring.
e.g. q_title=divine
q_author
string
Author-only substring.
e.g. q_author=moon
author
string
Exact (case-insensitive) author match.
e.g. author=Sun%20Myung%20Moon
min_words
integer
Minimum word count.
e.g. min_words=10000
max_words
integer
Maximum word count.
e.g. max_words=50000
has_book
boolean
true → only books with at least one chapter parsed out.
e.g. has_book=true
sort
enumalphabetical | length_asc | length_desc
Sort order. Date sorts do not apply to books (no per-book date in the corpus).
e.g. sort=length_desc
limit
integer
1..200, default 50.
e.g. limit=10
offset
integer
>= 0, default 0.
e.g. offset=10
format
enumjson | jsonl | csv
Response format. Default json (envelope). jsonl/csv ship just the row slice.
e.g. format=csv

Example

curl 'https://wiki.lineage-of-legends.com/api/v1/books?author=Sun%20Myung%20Moon&sort=length_desc&limit=2'
{
  "count": 2,
  "total": 47,
  "offset": 0,
  "limit": 2,
  "hasMore": true,
  "nextOffset": 2,
  "filters": { "author": "sun myung moon", "sort": "length_desc", ... },
  "books": [
    { "slug": "cheon-seong-gyeong", "title": "Cheon Seong Gyeong", "author": "Sun Myung Moon", "source": "CSG", "pageCount": 2542, "coverUrl": "/covers/cheon-seong-gyeong.jpg", "hasCover": true, "wordCount": 1024812 },
    ...
  ]
}
GET/api/v1/books/{slug}bucket: content · cache: 300s

Single book meta + chapter list. The chapter rows carry the paragraph id of the first paragraph in each chapter — pass them to /api/v1/books/{slug}/text to stream chapter contents.

Query params

slug*
string
The book slug (see list endpoint).
e.g. divine-principle

Example

curl https://wiki.lineage-of-legends.com/api/v1/books/divine-principle
{
  "slug": "divine-principle",
  "title": "Exposition of the Divine Principle",
  "author": "Hyo Won Eu (compiled), HSA-UWC",
  "pageCount": 295,
  "paragraphCount": 8423,
  "coverUrl": "/covers/divine-principle.jpg",
  "chapters": [
    { "number": 1, "title": "The Principle of Creation", "page": 30, "firstParagraphId": "p-30-1" },
    ...
  ],
  "textUrl": "/api/v1/books/divine-principle/text"
}
GET/api/v1/books/{slug}/textbucket: content · cache: 300s

Stream paragraphs from a book. Pass `from` and/or `to` paragraph ids to slice; otherwise you get the first 200 paragraphs.

Query params

from
string
Inclusive start paragraph id.
e.g. from=p-30-1
to
string
Inclusive end paragraph id.
e.g. to=p-45-2

Example

curl 'https://wiki.lineage-of-legends.com/api/v1/books/divine-principle/text?from=p-30-1&to=p-45-2'
{
  "slug": "divine-principle",
  "from": "p-30-1",
  "to": "p-45-2",
  "count": 142,
  "totalCount": 8423,
  "truncated": false,
  "nextFrom": null,
  "paragraphs": [
    { "paraId": "p-30-1", "page": 30, "text": "Chapter 1" },
    ...
  ]
}

Speeches

19,245-speech corpus mirrored from tparents.org. Author + year/decade filters, word-count ranges, sortable, JSONL/CSV export. Each speech also exposes the polished paragraph body via its detail endpoint.

GET/api/v1/speechesbucket: content · cache: 300s

Paginated speech listing with the full shared filter contract. Body text is not searched here — use /api/v1/search for that.

Query params

q
string
Substring across title + canonical author.
e.g. q=blessing
q_title
string
Title-only substring.
e.g. q_title=heart
q_author
string
Author-only substring.
e.g. q_author=Hak
author
string
Exact (case-insensitive) canonical display_author.
e.g. author=Sun%20Myung%20Moon
route_author
string
On-disk folder slug.
e.g. route_author=sunmyungmoon77
year
string
YYYY — date_guess starts with year.
e.g. year=1977
decade
string
YYY0s — speech falls in decade.
e.g. decade=1970s
date_from
string
YYYY[-MM[-DD]] lower bound.
e.g. date_from=1977-01
date_to
string
YYYY[-MM[-DD]] upper bound.
e.g. date_to=1977-12-31
source_kind
enumhtm | pdf
Origin file kind.
e.g. source_kind=htm
min_words
integer
Minimum word count.
max_words
integer
Maximum word count.
tag
string (repeatable)
Tag must be present. Pass multiple times to AND-combine.
exclude_tag
string (repeatable)
Tag must NOT be present.
sort
enumdate_asc | date_desc | alphabetical | length_asc | length_desc
Sort order.
limit
integer
1..200, default 50.
offset
integer
>= 0, default 0.
format
enumjson | jsonl | csv
Response format.

Example

curl 'https://wiki.lineage-of-legends.com/api/v1/speeches?author=Sun%20Myung%20Moon&decade=1970s&sort=date_asc&limit=2'
{
  "count": 2,
  "total": 1284,
  "offset": 0,
  "limit": 2,
  "hasMore": true,
  "nextOffset": 2,
  "filters": { ... },
  "speeches": [
    {
      "slug": "the-way-our-family-must-go",
      "routeAuthor": "sunmyungmoon77",
      "author": "Sun Myung Moon",
      "rawAuthor": "SunMyungMoon77",
      "title": "The Way Our Family Must Go",
      "dateGuess": "1977-10-23",
      "sourceUrl": "https://tparents.org/...",
      "sourceKind": "htm",
      "wordCount": 4912,
      "textUrl": "/api/v1/speeches/sunmyungmoon77/the-way-our-family-must-go"
    },
    ...
  ]
}
GET/api/v1/speeches/authorsbucket: content · cache: 300s

Speaker index — canonical display_author + speech count + on-disk route slug. Use this to discover speakers without paginating the full /speeches list.

Example

curl https://wiki.lineage-of-legends.com/api/v1/speeches/authors
{
  "count": 412,
  "totalSpeeches": 19245,
  "authors": [
    { "author": "Sun Myung Moon", "routeAuthor": "sunmyungmoon", "count": 12871 },
    { "author": "Hak Ja Han",     "routeAuthor": "hakjahan",     "count": 1442 },
    ...
  ]
}
GET/api/v1/speeches/{author}/{slug}bucket: content · cache: 300s

Full transcript — metadata plus the polished paragraph array. `author` is the on-disk folder slug (`routeAuthor` in the list response).

Query params

author*
string
On-disk folder slug.
e.g. sunmyungmoon77
slug*
string
Speech slug.
e.g. the-way-our-family-must-go

Example

curl https://wiki.lineage-of-legends.com/api/v1/speeches/sunmyungmoon77/the-way-our-family-must-go
{
  "slug": "the-way-our-family-must-go",
  "routeAuthor": "sunmyungmoon77",
  "author": "Sun Myung Moon",
  "rawAuthor": "SunMyungMoon77",
  "title": "The Way Our Family Must Go",
  "dateGuess": "1977-10-23",
  "sourceUrl": "https://tparents.org/...",
  "wordCount": 4912,
  "paragraphCount": 87,
  "paragraphs": [ "...", "...", ... ],
  "anchorPrefix": "p-"
}

Testimonies

Living-member faith-journey stories — podcast guests, written memoirs, YouTube videos, Substack pieces. Date filters use `episodeRef` (best-effort year extraction) so they are most useful on written memoirs.

GET/api/v1/testimoniesbucket: content · cache: 300s

Paginated testimony listing. q searches over name + tagline + summary + content body — testimony bodies are small enough that body search is cheap here (unlike speeches/books).

Query params

q
string
Substring across name + tagline + summary + body content.
q_title
string
Substring on name + tagline (closest analogue to title).
q_author
string
Substring on speaker name.
author
string
Exact (case-insensitive) speaker name.
language
string
BCP-47 lang code (en, ja, ko, de, …).
country
string
ISO 3166-1 alpha-2.
status
enumfull | preview-only | needs-audio
Content / curation status.
source_kind
enumpodcast | youtube | written-book | substack | other
Testimony source category.
tag
string (repeatable)
Tag must be present. Pass multiple times to AND-combine.
exclude_tag
string (repeatable)
Tag must NOT be present.
has_audio
boolean
true → only rows with audioUrl set.
has_image
boolean
true → only rows with imageUrl set.
has_book
boolean
true → only rows where the person has authored books.
year
string
YYYY — matches episodeRef contains year.
decade
string
YYY0s — matches episodeRef contains year in decade.
date_from
string
YYYY[-MM[-DD]] lower bound (vs episodeRef year).
date_to
string
YYYY[-MM[-DD]] upper bound.
min_words
integer
Minimum content word count.
max_words
integer
Maximum content word count.
sort
enumdate_asc | date_desc | alphabetical | length_asc | length_desc
Sort order. Missing-date rows sink to the end on date sorts.
limit
integer
1..200, default 50.
offset
integer
>= 0, default 0.
format
enumjson | jsonl | csv
Response format. jsonl/csv ship the row slice without the envelope.

Example

curl 'https://wiki.lineage-of-legends.com/api/v1/testimonies?source_kind=podcast&country=US&limit=2'
{
  "count": 2,
  "total": 41,
  "offset": 0,
  "limit": 2,
  "hasMore": true,
  "nextOffset": 2,
  "filters": { ... },
  "testimonies": [
    {
      "slug": "hon-mark-anderson-wij",
      "name": "Hon. Mark Anderson",
      "tagline": "Politics and Religion",
      "summary": "...",
      "source": "podcast",
      "sourceName": "Why I Joined",
      "status": "full",
      "language": "en",
      "country": "US",
      "hasAudio": true,
      "hasImage": true,
      "hasBooks": false,
      "wordCount": 7421,
      "audioUrl": "...",
      "imageUrl": "...",
      "url": "https://...",
      "textUrl": "/api/v1/testimonies/hon-mark-anderson-wij",
      "relatedLegendSlug": null
    },
    ...
  ]
}
GET/api/v1/testimonies/{slug}bucket: content · cache: 300s

Full testimony record by slug — including all content paragraphs, books, papers, and tags. 404 with `testimony_not_found` on miss.

Query params

slug*
string
Testimony slug.

Example

curl https://wiki.lineage-of-legends.com/api/v1/testimonies/hon-mark-anderson-wij
{
  "slug": "hon-mark-anderson-wij",
  "name": "Hon. Mark Anderson",
  "tagline": "Politics and Religion",
  "summary": "...",
  "content": [ "Hosts: ...", "Mark: ...", ... ],
  "source": "podcast",
  "sourceName": "Why I Joined",
  "status": "full",
  "tags": ["first-gen", "politician"],
  "language": "en",
  "country": "US",
  "wordCount": 7421,
  ...
}
GET/api/v1/testimonies/sourcesbucket: content · cache: 300s

Source-bucket index: one row per testimony.source value with a count. Sorted by count desc.

Example

curl https://wiki.lineage-of-legends.com/api/v1/testimonies/sources
{
  "count": 4,
  "sources": [
    { "source": "podcast",      "count": 51 },
    { "source": "youtube",      "count": 28 },
    { "source": "written-book", "count": 14 },
    { "source": "substack",     "count": 4 }
  ]
}

Stability + versioning

This is v1. We won't make breaking changes inside /api/v1/* — new fields may appear but existing ones won't be renamed or removed. Bigger changes ship under /api/v2.