openapi: 3.0.3

# Machine-readable contract for the public API. DOCS.md stays the prose
# reference — this is the one tools read: RapidAPI Studio imports it as the
# listing definition, and client SDKs are generated from it.
#
# Kept in sync by an automated check against the live router: a route missing
# here, or documented here but not served, fails the test suite. A spec that
# drifts is worse than no spec, because it is trusted.

info:
  title: IQ Bible API
  version: "2.0.0-beta-17"
  description: |
    Bible text across many translations and languages, plus extrabiblical works,
    lexicons, original-language (Hebrew/Greek) source text, and chapter audio narration.

    Every public endpoint is `GET` and returns JSON (UTF-8, NFC-normalised).

    **Authentication.** An API key is required on every endpoint. Send it as an
    `X-IQBible-API-Key` header (preferred), an `Authorization: Bearer` token, or
    an `?apikey=` query parameter. RapidAPI subscribers are authenticated by the
    RapidAPI proxy instead and need no separate key.

    **Rate limits.** Each plan carries a per-minute rate and a monthly quota,
    enforced per account — all of your keys share one budget. Every response
    carries `X-RateLimit-*` headers. A few compute-heavy endpoints cost more than
    one slot of the *per-minute* budget (search 5, search/count 3, the full
    catalogue 2); the monthly quota always counts one call as one request.

    **Past your monthly quota** behaviour depends on the plan: Free is a hard cap
    and returns `429 quota_exceeded`, while paid plans keep serving and bill the
    excess per 1,000, bounded at 10x the quota (`429 overage_limit_reached`).

    **Audio** has its own monthly allowance counted in streams — one signed URL
    minted by the audio endpoint, counted only when the mint succeeds.
  license:
    name: Proprietary
  contact:
    name: IQ Bible API support
    email: support@iqbible.com

servers:
  - url: https://api.iqbible.com/api/v2
    description: Production
  - url: http://localhost:9000/api/v2
    description: Local development

tags:
  - name: Account
    description: The calling API key's own plan, quota, and usage.
  - name: Audio narrations
    description: Chapter narration, delivered as short-lived signed URLs.
  - name: Bibles · Catalog & versions
    description: The translation catalog, languages, and per-translation book lists.
  - name: Bibles · Reading & discovery
    description: Full-text search, random verse/chapter, verse of the day, and reading plans.
  - name: Bibles · Text & verses
    description: Reading passages, batch verse fetch, word lists, and words-of-Jesus markup.
  - name: Bibles · Word counts
    description: Word counts at the bible, book, chapter, and verse level.
  - name: Books
    description: Version-independent reference content about a book (introduction, authorship, themes).
  - name: Extrabiblical works
    description: Non-canonical works (apocryphal and pseudepigraphal texts).
  - name: Growth
    description: Public, unauthenticated embed/share endpoints (no API key) — rate-limited per IP instead.
  - name: Meta · Curated lists
    description: Bible stories, parables, prophecies, prayers, miracles, and names/titles of God and Jesus.
  - name: Meta · Info & legal
    description: Service status, credits, and licensing terms.
  - name: Meta · People & history
    description: Genealogies, Bible-character profiles, gospel harmony, and chronology.
  - name: Meta · Textual notes & tools
    description: Textual variants, weights/measures, and reading-time estimation.
  - name: Meta · Topics
    description: Topical indexing across the whole canon.
  - name: Study · AI, parsing & embeds
    description: LLM-ready context bundling and free-text citation parsing.
  - name: Study · Commentaries & dictionaries
    description: Verse-by-verse commentary sources and Bible dictionary lookups.
  - name: Study · Geography
    description: Bible places mentioned at a reference, and the place gazetteer.
  - name: Study · Icons & illustrations
    description: Book icons and staged Bible illustrations by artist/work.
  - name: Study · Lexicon (Greek/Hebrew)
    description: Strong's, BDB, LSJ, and Abbott-Smith lexicon entries, plus the Greek/Hebrew alphabets.
  - name: Study · Original language & cross-refs
    description: Word-by-word Hebrew/Greek source text and verse-to-verse cross-references.

paths:
  /info:
    get:
      tags: [Meta · Info & legal]
      operationId: getInfo
      summary: API status and content counts
      description: >-
        Service health, version, and current content inventory (Bible versions,
        extrabiblical works, languages, commentaries, dictionaries, endpoints).
        Each count is best-effort — a field is absent if its own query fails
        rather than failing the whole response.
      responses:
        "200":
          description: Service metadata.
          headers:
            X-RateLimit-Limit:
              description: Per-minute request limit.
              schema: { type: integer }
            X-RateLimit-Remaining:
              description: Requests left in the current minute.
              schema: { type: integer }
            X-RateLimit-Remaining-Month:
              description: Requests left this month.
              schema: { type: integer }
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string, example: "IQ Bible API" }
                  version: { type: string, example: "2.0.0-alpha-73" }
                  status: { type: string, example: "ok" }
                  base: { type: string, example: "/api/v2" }
                  bible_versions: { type: integer, example: 137 }
                  extrabiblical_works: { type: integer, example: 16 }
                  languages: { type: integer, example: 62 }
                  commentaries: { type: integer, example: 326 }
                  dictionaries: { type: integer, example: 5 }
                  endpoints: { type: integer, example: 118 }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /credits:
    get:
      tags: [Meta · Info & legal]
      operationId: getCredits
      summary: Third-party sources and attributions
      description: |
        The third-party sources and attributions behind the API's content
        (Bible text collections, audio, commentaries, dictionaries,
        illustrations, geocoding, and more), as structured JSON. Grouped at
        the source-collection level for Bible texts (ebible.org,
        wordproject.org, ...) rather than one row per translation — a
        specific translation's own license is on its entry in `GET /bibles`.

        The same content is published without an API key at
        `https://api.iqbible.com/credits.md` (prose) — see also
        `full_text_url` in the response.
      responses:
        "200":
          description: Attribution list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  sources:
                    type: array
                    items:
                      type: object
                      properties:
                        category: { type: string, example: "bible_texts" }
                        name: { type: string, example: "ebible.org" }
                        url: { type: string, example: "https://ebible.org" }
                        covers: { type: string }
                        license: { type: string }
                        detailed_at: { type: string }
                  full_text_url: { type: string, example: "https://api.iqbible.com/credits.md" }
                  note: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /licensing:
    get:
      tags: [Meta · Info & legal]
      operationId: getLicensing
      summary: Machine-readable summary of your license to use the API
      description: |
        Cache policy, resale/scraping restrictions, and the categories every
        Bible translation and dataset's license is classified into
        (`public_domain`, `cc0`, `cc_permissive`, `owned`, `cc_restrictive`,
        `copyrighted`, `unknown`). A summary, not the full terms — see
        `full_text_url`, or the public, key-free
        `https://api.iqbible.com/licensing.md`, for the complete text you
        agree to by subscribing.
      responses:
        "200":
          description: Licensing summary.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  summary:
                    type: object
                    properties:
                      license_type: { type: string }
                      caching_allowed: { type: boolean }
                      caching_max_hours: { type: integer }
                      caching_requires: { type: string }
                      resale_prohibited: { type: boolean }
                      scraping_prohibited: { type: boolean }
                      competing_use_barred: { type: boolean }
                  license_categories:
                    type: object
                    additionalProperties: { type: string }
                  full_text_url: { type: string, example: "https://api.iqbible.com/licensing.md" }
                  credits_url: { type: string, example: "https://api.iqbible.com/credits.md" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /me:
    get:
      tags: [Account]
      operationId: getMe
      summary: Your plan, quota, and remaining usage
      description: |
        The calling API key's own plan and this billing period's request and
        audio quota, scoped to the key presented on this call — never a
        client-supplied id. Lets a client library self-throttle from a number
        instead of learning its limit from a `429`.

        Requires an actual API key: the RapidAPI proxy secret and an admin
        session (both otherwise accepted by the gate) return `401
        api_key_required` here, since there is no single key's usage to report
        for either. A RapidAPI-proxied call gets a hint pointing to the
        developer portal (RapidAPI manages your subscription internally, so
        there's nothing per-key to report there) rather than the generic
        "send an API key" hint an admin session gets.

        `remaining` is `null` when the meter is unlimited (`limit_per_month:
        0`) or, for audio, disabled for the plan (`limit_per_month: -1`) —
        either way there is no ceiling to report a balance against.
      responses:
        "200":
          description: Current plan, key, billing period, and quota.
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan:
                    type: object
                    properties:
                      id: { type: string, example: "pro" }
                      name: { type: string, example: "Pro" }
                      price_cents: { type: integer, example: 1900, description: "0 = free, -1 = custom (enterprise)" }
                  key:
                    type: object
                    properties:
                      id: { type: string }
                      name: { type: string, example: "Production" }
                      prefix: { type: string, example: "iqb_live_ab12" }
                  period:
                    type: object
                    description: The current UTC calendar month, or later if an admin reset your quota mid-month.
                    properties:
                      start: { type: string, format: date-time }
                      end: { type: string, format: date-time }
                  requests:
                    $ref: "#/components/schemas/MeMeter"
                  audio:
                    $ref: "#/components/schemas/MeMeter"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /stories:
    get:
      tags: [Meta · Curated lists]
      operationId: listStories
      summary: Curated Bible stories
      description: A curated list of Bible stories with title, summary, reference and era.
      responses:
        "200":
          description: The story list.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /parables:
    get:
      tags: [Meta · Curated lists]
      operationId: listParables
      summary: All parables, with Scripture references
      description: |
        Every parable's title and its Scripture reference(s) — several parables
        appear in more than one Synoptic Gospel, so `references` can hold
        multiple citations. Each reference gives a USFM book code, chapter,
        and verse (plus `verse_end` for a range) alongside a human-readable
        `citation` string.
      parameters:
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The parable list.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /prophecies:
    get:
      tags: [Meta · Curated lists]
      operationId: listProphecies
      summary: Old Testament prophecies fulfilled in Jesus
      description: |
        Every Old Testament prophecy traditionally understood as fulfilled in
        Jesus — an `origin` reference, a `description`, and one or more
        `fulfilled_in` New Testament references. Both `origin` and each
        `fulfilled_in` entry are USFM-coded (chapter/verse, `verse_end` for a
        range, `chapter_end`/`book_end` for the rare range that crosses a
        chapter or book boundary) alongside a human-readable `citation`
        string. This is an array, not a map keyed by origin verse — several
        prophecies legitimately share the same origin verse (e.g. Isaiah
        9:6), so more than one entry can have an identical `origin`.
      parameters:
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The prophecy list.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /prayers:
    get:
      tags: [Meta · Curated lists]
      operationId: listPrayers
      summary: Every prayer of the Bible
      description: |
        Every prayer of the Bible — who prayed it (`pray_er`), its category
        (Petition, Intercession, Confession, Praise, Lament, Dedication,
        Blessing), a one-sentence `context`, and its Scripture reference(s).
        Several prayers appear in more than one Gospel (the Lord's Prayer,
        Gethsemane), so `references` can hold multiple citations, same
        USFM-coded shape as `/parables`.

        A handful of prayers are deuterocanonical/apocryphal and carry a
        `canon` field (e.g. `"Catholic"`); every other prayer omits it,
        meaning universal/protocanonical.
      parameters:
        - name: canon
          in: query
          description: |
            Filter to universal (protocanonical) prayers plus this
            tradition's own, matched case-insensitively against a Bible
            tradition name (e.g. `protestant`, `catholic`,
            `eastern orthodox`). Omitted returns everything. An
            unrecognized name returns 400.
          schema: { type: string }
      responses:
        "200":
          description: The prayer list.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /miracles:
    get:
      tags: [Meta · Curated lists]
      operationId: listMiracles
      summary: Every miracle of the Bible
      description: |
        Every miracle of the Bible — a natural sibling to `/parables`, same
        USFM-coded `references` shape, plus `performed_by` and a `category`
        (Nature, Healing, Exorcism, Resurrection, Provision, Deliverance,
        Judgment, Sign). Same optional `canon` filter and deuterocanon
        tagging convention as `/prayers`.
      parameters:
        - name: canon
          in: query
          description: |
            Filter to universal (protocanonical) miracles plus this
            tradition's own, matched case-insensitively against a Bible
            tradition name. Omitted returns everything. An
            unrecognized name returns 400.
          schema: { type: string }
      responses:
        "200":
          description: The miracle list.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /names-of-god:
    get:
      tags: [Meta · Curated lists]
      operationId: listNamesOfGod
      summary: Names and titles of God
      description: |
        A curated list of names and titles of God (name, meaning, Scripture
        reference, testament) — e.g. Elohim, Yahweh, El Shaddai, Jehovah
        Jireh. `reference` is free text, same convention as `/stories`. No
        parameters.
      responses:
        "200":
          description: The names-of-God list.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /titles-of-jesus:
    get:
      tags: [Meta · Curated lists]
      operationId: listTitlesOfJesus
      summary: Names and titles of Jesus
      description: |
        A curated list of names and titles of Jesus (name, meaning,
        Scripture reference, testament) — e.g. Messiah, Son of Man, Lamb of
        God, the Good Shepherd. Same free-text-reference shape as
        `/names-of-god`. No parameters.
      responses:
        "200":
          description: The titles-of-Jesus list.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /reference/weights-measures:
    get:
      tags: [Meta · Textual notes & tools]
      operationId: listWeightsMeasures
      summary: Biblical weights, measures, and currency
      description: |
        Classic study-Bible back matter: biblical weights, measures, and
        currency (talents, cubits, shekels, denarii) with modern-unit
        approximations. No parameters.
      responses:
        "200":
          description: The weights/measures list.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /topics:
    get:
      tags: [Meta · Topics]
      operationId: listTopics
      summary: All topic names
      description: |
        The full list of topic names — pass one to `GET /topics/{topic}` for
        its citations. Two editions exist: `iqbible` (default, 97
        hand-curated pastoral topics like `addiction`, `forgiveness`) and
        `nave-torrey` (5,745 topics unified from Nave's Topical Bible, 1896,
        and Torrey's New Topical Textbook, 1897 — an exhaustive
        concordance-style index, e.g. `AARON`, `FAITH`). 76 names exist in
        both; `edition=all` lists both without deduplicating.
      parameters:
        - name: edition
          in: query
          description: Which topic edition to list.
          schema: { type: string, enum: [iqbible, nave-torrey, all], default: iqbible }
      responses:
        "200":
          description: The topic name list.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /topics/{topic}:
    get:
      tags: [Meta · Topics]
      operationId: getTopic
      summary: Scripture citations for a topic
      description: |
        Citations supporting a topic (e.g. `love`, `anxiety`), matched
        case-insensitively. Each citation gives a human-readable reference
        plus its individual verses, USFM-coded — a multi-verse citation like
        "James 1:12-15" is broken out into its four verses. `nave-torrey`
        citations also carry `label`, the Nave/Torrey aspect heading the
        reference is grouped under. See `GET /topics` for the full list of
        valid names.

        76 names exist in both editions; omitting `edition` prefers
        `iqbible` when both have the name, else resolves to whichever
        edition has it. The response's `edition` field reports which one
        was actually served.
      parameters:
        - name: topic
          in: path
          required: true
          schema: { type: string }
          example: love
        - name: edition
          in: query
          description: Restrict the lookup to one edition. Omit to prefer `iqbible` when the name exists in both.
          schema: { type: string, enum: [iqbible, nave-torrey] }
      responses:
        "200":
          description: The topic's citations.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /topics/{book}/{chapter}:
    get:
      tags: [Meta · Topics]
      operationId: getVerseTopicsChapter
      summary: Every topic tagging a book/chapter
      description: |
        The reverse of `GET /topics/{topic}`: every topic whose citation
        touches this book/chapter, rather than a topic's own citation list.
        Same underlying data, no new dataset. A multi-verse citation (e.g.
        "James 1:12-15") that touches this chapter is collapsed to one entry
        even though it spans several verses.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: edition
          in: query
          description: Restrict to one topic edition.
          schema: { type: string, enum: [iqbible, nave-torrey, all], default: all }
      responses:
        "200":
          description: Topics touching that chapter.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /topics/{book}/{chapter}/{verse}:
    get:
      tags: [Meta · Topics]
      operationId: getVerseTopicsVerse
      summary: Every topic tagging a specific verse
      description: |
        Same as the chapter-level route, narrowed to one verse — e.g. John
        3:16 alone carries dozens of topic tags across both editions
        (`faith`, `love of god, the`, `eternal life`, ...).
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, example: 16 }
        - name: edition
          in: query
          description: Restrict to one topic edition.
          schema: { type: string, enum: [iqbible, nave-torrey, all], default: all }
      responses:
        "200":
          description: Topics tagging that verse.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /genealogies/{name}:
    get:
      tags: [Meta · People & history]
      operationId: getGenealogyPerson
      summary: A name's recorded parents and children
      description: |
        Every recorded parent and child for a name, matched
        case-insensitively against exact KJV spelling. Derived from Genesis
        5/10/11, 1 Chronicles 1-9, Matthew 1, and Luke 3's existing text,
        not a hand-curated dataset — some of
        1 Chronicles' denser tribal listings don't parse cleanly enough to
        trust automatically, so a handful of relationships aren't recorded.
        Either list can have more than one entry for the same relative when
        more than one chapter records it (e.g. 1 Chronicles 1 recaps
        Genesis 10's Table of Nations).
      parameters:
        - name: name
          in: path
          required: true
          schema: { type: string }
          example: Noah
      responses:
        "200":
          description: The person's recorded parents and children.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /genealogies:
    get:
      tags: [Meta · People & history]
      operationId: getGenealogyPath
      summary: Shortest recorded lineage path between two names
      description: |
        Walks the same data as `GET /genealogies/{name}` as an undirected
        graph to find the shortest chain of relationships connecting two
        names — works in either direction (ancestor->descendant or
        descendant->ancestor), and across a path that climbs one line and
        descends another via a shared ancestor. Each step after the first
        carries `direction` (`descendant` or `ancestor`, relative to the
        previous step) plus the citation for the edge connecting it.
      parameters:
        - name: from
          in: query
          required: true
          schema: { type: string }
          example: Adam
        - name: to
          in: query
          required: true
          schema: { type: string }
          example: Noah
      responses:
        "200":
          description: The ordered path connecting the two names.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bible-characters/{name}:
    get:
      tags: [Meta · People & history]
      operationId: getBibleCharacter
      summary: A composed profile for a named Bible person
      description: |
        Composed at request time from three datasets that already power
        other endpoints, matched case-insensitively — no dedicated dataset
        of its own. `definitions` come from every dictionary source with an
        entry for the name (Easton's, Smith's, Hastings', Hitchcock's,
        Schaff's — see `GET /dictionaries/{dictionaryId}`), `key_events`
        come from the name's Nave's Topical Bible / Torrey's New Topical
        Textbook entry (see `GET /topics/{topic}`), and `parents`/`children`
        come from `GET /genealogies/{name}` when the name appears there. A
        name can match one, two, or all three sources; unmatched fields are
        omitted rather than returned empty, so a genealogy-only figure with
        no dictionary or topical entry still returns a partial profile
        instead of 404ing. `first_appearance`/`last_appearance` are the
        earliest/latest citation in canonical book order from the Nave/
        Torrey entry specifically, omitted when there's no topical entry.
      parameters:
        - name: name
          in: path
          required: true
          schema: { type: string }
          example: Moses
      responses:
        "200":
          description: The character's composed profile.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /harmony/{section}:
    get:
      tags: [Meta · People & history]
      operationId: getHarmonySection
      summary: One narrative-event section aligning the Gospels' parallel accounts
      description: |
        A. T. Robertson's 1922 *A Harmony of the Gospels* — aligns
        Matthew/Mark/Luke/John's parallel accounts of the same event, a
        different axis than `GET /bibles/parallel`'s same-verse-across-
        versions. `{section}` is Robertson's own citable numbering (e.g.
        `72`), not a sequential index — `128` splits into `128a`/`128b` in
        the source. A reference's `chapter_end`/`verse_end` differ from
        `chapter_start`/`verse_start` only for a citation spanning chapters;
        `verse_start`/`verse_end` are omitted for a whole-chapter citation. A
        few sections cite Acts 1 or 1 Corinthians 15 for post-resurrection
        events the Gospels themselves don't record.
      parameters:
        - name: section
          in: path
          required: true
          schema: { type: string }
          example: "72"
      responses:
        "200":
          description: The section's title, part, and full parallel reference set.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /harmony:
    get:
      tags: [Meta · People & history]
      operationId: listHarmony
      summary: All harmony sections, or those touching a given book/chapter
      description: |
        With no params, every section (185 total) in Robertson's own order.
        With `book`/`chapter` (required together), every section whose
        reference set touches that book/chapter — each still carrying its
        complete cross-Gospel reference list, not just the citation that
        matched the filter.
      parameters:
        - name: book
          in: query
          description: USFM code or English name. Required together with chapter.
          schema: { type: string, example: "MAT" }
        - name: chapter
          in: query
          description: Chapter number. Required together with book.
          schema: { type: integer, example: 14 }
      responses:
        "200":
          description: Matching harmony sections.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /textual-variants:
    get:
      tags: [Meta · Textual notes & tools]
      operationId: listTextualVariants
      summary: Every known New Testament textual crux in the dataset
      description: |
        The full "Manuscript Transparency" dataset — every crux in the set,
        in canon order, no book/chapter filter. IQ Bible-compiled, informed
        by the public-domain textual-critical tradition rather than a full
        critical apparatus.
      responses:
        "200":
          description: Every textual-variant entry.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /textual-variants/{book}/{chapter}:
    get:
      tags: [Meta · Textual notes & tools]
      operationId: getTextualVariantsByChapter
      summary: Textual variants touching a book/chapter
      description: |
        Where the KJV/Textus Receptus/Byzantine tradition and the modern
        critical text diverge, for the crux(es) touching this chapter — each
        with the traditional and critical readings, supporting
        manuscripts/versions, and a plain-language explanation. A crux
        spanning a chapter boundary (e.g. John 7:53-8:11) is returned in
        full whenever the request lands anywhere inside its range.
      parameters:
        - name: book
          in: path
          required: true
          schema: { type: string, example: "MRK" }
        - name: chapter
          in: path
          required: true
          schema: { type: integer, example: 16 }
      responses:
        "200":
          description: Textual variants touching that chapter.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /textual-variants/{book}/{chapter}/{verse}:
    get:
      tags: [Meta · Textual notes & tools]
      operationId: getTextualVariantsByVerse
      summary: Textual variants touching a specific verse
      description: |
        Same as the chapter-level route, narrowed to one verse. Matches any
        crux whose range covers that verse, not just one starting there.
      parameters:
        - name: book
          in: path
          required: true
          schema: { type: string, example: "MRK" }
        - name: chapter
          in: path
          required: true
          schema: { type: integer, example: 16 }
        - name: verse
          in: path
          required: true
          schema: { type: integer, example: 9 }
      responses:
        "200":
          description: Textual variants touching that verse.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles:
    get:
      tags: [Bibles · Catalog & versions]
      operationId: listBibles
      summary: List all translations (the catalogue)
      description: |
        The full translation catalogue. This is the heaviest response the API
        serves (~93 KB unfiltered) and costs **2** slots of the per-minute
        budget — cache it rather than calling it per request.
      parameters:
        - name: language_code
          in: query
          description: Limit to one ISO 639-3 language.
          schema: { type: string, example: "eng" }
        - name: language_name
          in: query
          description: Limit by English language name.
          schema: { type: string, example: "English" }
      responses:
        "200":
          description: Matching translations.
          headers:
            X-RateLimit-Limit:
              description: Per-minute request limit.
              schema: { type: integer }
            X-RateLimit-Remaining:
              description: Requests left in the current minute.
              schema: { type: integer }
            X-RateLimit-Remaining-Month:
              description: Requests left this month.
              schema: { type: integer }
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  count: { type: integer }
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Version" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/languages:
    get:
      tags: [Bibles · Catalog & versions]
      operationId: listLanguages
      summary: List available languages
      description: The distinct languages available across all translations.
      responses:
        "200":
          description: Distinct languages.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/parallel:
    get:
      tags: [Bibles · Catalog & versions]
      operationId: getParallelVerse
      summary: Compare one verse across translations
      description: |
        Returns the same verse from several translations side by side.

        Version codes that don't exist — or that don't carry the requested verse
        — are **skipped rather than erroring**, so check the returned `compare`
        array for the versions you actually asked for.
      parameters:
        - name: v
          in: query
          required: true
          description: Comma-separated version codes.
          schema: { type: string, example: "eng_kjv,spa_rv" }
        - name: book
          in: query
          required: true
          description: >-
            USFM code, English name, or the canon-wide master book id — distinct from a
            translation's own local book id, which isn't accepted here since `v` can list
            several versions' own numbering at once.
          schema: { type: string, example: "JHN" }
        - name: ch
          in: query
          required: true
          schema: { type: integer, minimum: 1, example: 3 }
        - name: v_num
          in: query
          required: true
          schema: { type: integer, minimum: 1, example: 16 }
      responses:
        "200":
          description: The verse in each recognised version.
          content:
            application/json:
              schema:
                type: object
                properties:
                  book: { type: string, example: "JHN" }
                  chapter_number: { type: integer }
                  verse_number: { type: integer }
                  usfm: { type: string }
                  compare:
                    type: array
                    items:
                      type: object
                      properties:
                        version: { type: string }
                        text: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/books:
    get:
      tags: [Bibles · Catalog & versions]
      operationId: listBooks
      summary: List a translation's books
      description: >-
        The books of a translation, with that version's local ids and USFM code. `id` is only
        valid for other `{version}`-scoped endpoints — a version-independent endpoint
        (cross-references, icons, illustrations, geo, harmony, …) resolves `book=` against a
        separate canon-wide id that doesn't always match a version's own local numbering; `usfm`
        is unambiguous either way.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: testament
          in: query
          description: >-
            Restrict to one testament. Matched by USFM code membership in the 27 canonical
            NT books, not a numeric id cutoff, so it stays correct regardless of a version's
            local book numbering and for traditions (Ethiopian, Orthodox, …) with extra
            OT-side books.
          schema: { type: string, enum: [ot, nt] }
      responses:
        "200":
          description: Books in canonical order.
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  testament:
                    type: string
                    enum: [ot, nt]
                    description: Present only when the `testament` filter was applied.
                  count: { type: integer }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        name: { type: string }
                        usfm: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/meta:
    get:
      tags: [Bibles · Text & verses]
      operationId: getBookMeta
      summary: Chapter and verse counts for a book
      description: Retrieve the chapter and verse structure of a Bible book without fetching full text.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
      responses:
        "200":
          description: Chapter list with per-chapter verse counts.
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  book_id: { type: integer }
                  chapter_count: { type: integer }
                  chapters:
                    type: array
                    items:
                      type: object
                      properties:
                        chapter: { type: integer }
                        verse_count: { type: integer }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}:
    get:
      tags: [Bibles · Text & verses]
      operationId: getChapter
      summary: Read a whole chapter
      description: Fetch all verses in a Bible chapter, with optional parallel versions for comparison.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - $ref: "#/components/parameters/IncludeWordsOfJesus"
      responses:
        "200":
          description: The chapter's verses.
          content:
            application/json: { schema: { $ref: "#/components/schemas/Passage" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}/{verse}:
    get:
      tags: [Bibles · Text & verses]
      operationId: getVerse
      summary: Read a verse or verse range
      description: |
        `verse` may be a single verse (`16`) or a range (`1-10`). A range wider
        than **100 verses** returns `400 passage_too_large`.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          description: A single verse or an inclusive range, max 100 verses.
          schema: { type: string, example: "16" }
        - $ref: "#/components/parameters/IncludeWordsOfJesus"
      responses:
        "200":
          description: The requested verses.
          content:
            application/json: { schema: { $ref: "#/components/schemas/Passage" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}/{verse}/words:
    get:
      tags: [Bibles · Text & verses]
      operationId: getVerseWords
      summary: Word list for a verse
      description: |
        Splits the verse's stored text into individual words (a plain whitespace
        split — punctuation stays attached, e.g. "earth." not "earth"). Unlike
        the verse/passage route, `verse` is required here — there's no
        whole-chapter mode.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
      responses:
        "200":
          description: The verse's words.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordsResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}/{verse}/words-of-jesus:
    get:
      tags: [Bibles · Text & verses]
      operationId: getVerseWordsOfJesus
      summary: Word-level words-of-Jesus (red-letter) markup for a verse
      description: |
        Returns the verse's full text plus, when available, `segments` — the
        verse split into ordered runs of text each tagged `is_jesus`, so a
        client can render only the words Jesus actually spoke in red rather
        than the whole verse. This is word-level, not verse-level: a coarser
        predecessor approach only flagged whole verses where Jesus spoke
        somewhere, with no way to tell narration from the quote within a
        mixed verse like Matthew 3:15 ("But Jesus, answering, said to him,
        'Allow it now...' Then he allowed him.").

        Segment data only exists for the subset of versions whose source
        text carried USFM `\wj` markup — most versions don't have it. `verse`
        text is always returned regardless; `has_words_of_jesus` is `false`
        and `segments` is empty both when this specific verse has no words
        of Jesus and when the version has no segmentation data at all. Check
        `words_of_jesus_count` on `GET /bibles` to tell those two cases apart
        for a given version before calling this per-verse.

        Joining `segments[].text` with a single space reconstructs the
        verse — segments are stored trimmed, not with the original
        inter-segment spacing.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          description: >-
            John 3:10 is a good example — it mixes narration and quoted speech in
            one verse, so segments shows both is_jesus true and false.
          schema: { type: integer, minimum: 1, example: 10 }
      responses:
        "200":
          description: The verse's text and, if available, its words-of-Jesus segments.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordsOfJesusResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/word-count:
    get:
      tags: [Bibles · Word counts]
      operationId: getBibleWordCount
      summary: Total word count for a whole bible/version
      description: |
        Total whitespace-split word count across every verse in the version —
        the same split rule as `GET .../{verse}/words`, summed instead of
        listed.
      parameters:
        - $ref: "#/components/parameters/Version"
      responses:
        "200":
          description: The version's total word count.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordCountResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/word-count:
    get:
      tags: [Bibles · Word counts]
      operationId: getBookWordCount
      summary: Total word count for a book
      description: |
        Total whitespace-split word count across every chapter of the book —
        the same split rule as `GET .../{verse}/words`, summed instead of
        listed.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
      responses:
        "200":
          description: The book's total word count.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordCountResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}/word-count:
    get:
      tags: [Bibles · Word counts]
      operationId: getChapterWordCount
      summary: Total word count for a chapter
      description: |
        Total whitespace-split word count across the chapter's verses.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
      responses:
        "200":
          description: The chapter's total word count.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordCountResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/{book}/{chapter}/{verse}/word-count:
    get:
      tags: [Bibles · Word counts]
      operationId: getVerseWordCount
      summary: Word count for a verse
      description: |
        Whitespace-split word count for a single verse — the count half of
        `GET .../{verse}/words` without the word list itself.
      parameters:
        - $ref: "#/components/parameters/Version"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
      responses:
        "200":
          description: The verse's word count.
          content:
            application/json: { schema: { $ref: "#/components/schemas/WordCountResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/search:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: searchVerses
      summary: Full-text search within a translation
      description: |
        Costs **5** slots of the per-minute budget. Paginate with `cursor` for
        `sort=canonical` and `offset` for `sort=relevance`; the response returns
        `next_cursor` or `next_offset` accordingly.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: q
          in: query
          required: true
          description: Search terms.
          schema: { type: string, example: "lovingkindness" }
        - name: match
          in: query
          description: |
            How terms combine. `all` = every term required, `any` = any term,
            `phrase` = exact phrase, `prefix` = each term as a `word*` wildcard,
            `boolean` = raw MySQL boolean-mode query. `exact` is an alias for `phrase`.
          schema:
            type: string
            default: all
            enum: [all, any, phrase, prefix, boolean, exact]
        - name: exclude
          in: query
          description: Terms to exclude from results.
          schema: { type: string }
        - name: book
          in: query
          description: >-
            Limit to book(s) — USFM/name/id, a comma list, or a numeric range
            (e.g. "Matthew,Mark"). No example value on purpose: RapidAPI's
            playground pre-fills every enum/text example simultaneously, and
            a book example here combined with a testament example below has
            previously pre-filled a self-contradictory filter (NT books with
            testament=ot) that returns zero results — see #218.
          schema: { type: string }
        - name: chapter
          in: query
          schema: { type: integer, minimum: 1 }
        - name: verse
          in: query
          description: A verse or inclusive range.
          schema: { type: string }
        - name: testament
          in: query
          description: >-
            Must agree with any `book=` filter above, or the combination
            returns nothing. No example value — see the note on `book` above.
          schema: { type: string, enum: [ot, nt] }
        - name: sort
          in: query
          schema: { type: string, default: canonical, enum: [canonical, relevance] }
        - name: highlight
          in: query
          description: Set to `1` to add a `highlight` field wrapping matches in `<mark>`.
          schema: { type: string, enum: ["1"] }
        - name: count
          in: query
          description: Set to `1` to include `total_matches`.
          schema: { type: string, enum: ["1"] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: cursor
          in: query
          description: Next page for `sort=canonical` — pass the response's `next_cursor`.
          schema: { type: integer }
        - name: offset
          in: query
          description: Next page for `sort=relevance` — pass the response's `next_offset`.
          schema: { type: integer }
      responses:
        "200":
          description: Matching verses.
          headers:
            X-RateLimit-Cost:
              description: This call's cost against the per-minute budget (5).
              schema: { type: integer }
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  query: { type: string }
                  match: { type: string }
                  sort: { type: string }
                  filters: { type: object }
                  total_matches: { type: integer }
                  results_count: { type: integer }
                  next_cursor: { type: integer }
                  next_offset: { type: integer }
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SearchResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/search/count:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: countSearchMatches
      summary: Count search matches
      description: A lightweight alternative to `count=1`. Costs **3** slots of the per-minute budget.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: q
          in: query
          required: true
          schema: { type: string }
        - name: match
          in: query
          schema: { type: string, default: all, enum: [all, any, phrase, prefix, boolean, exact] }
        - name: exclude
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Total match count.
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  query: { type: string }
                  match: { type: string }
                  total_matches: { type: integer }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/random/verse:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: getRandomVerse
      summary: A random verse
      description: |
        With no query params, picks from a curated pool of well-known,
        self-contained verses — not a uniform pick across every verse in the
        Bible, which would surface out-of-context fragments (genealogies,
        census lists) just as often as anything meaningful. Passing `book`
        and/or `chapter` scopes the pick to the real text within that scope
        (uniform random, no curation). Passing `keyword` picks among verses
        full-text-matching that word (also uniform random, no curation) and
        takes priority over the curated pool if both would apply.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: book
          in: query
          description: Limit to one book — USFM/name/id.
          schema: { type: string, example: "John" }
        - name: chapter
          in: query
          schema: { type: integer, minimum: 1 }
        - name: keyword
          in: query
          description: Full-text search term — the pick is random among matches.
          schema: { type: string, example: "love" }
      responses:
        "200":
          description: A single random verse.
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  source:
                    type: string
                    description: Which pool the pick came from.
                    enum: [popular, scoped, keyword]
                  filters: { type: object }
                  data: { $ref: "#/components/schemas/SearchResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/random/chapter:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: getRandomChapter
      summary: A random chapter
      description: |
        Uniformly random across chapters (not weighted by verse count), with
        no curation — a whole chapter reads fine even where an isolated verse
        from it wouldn't, so unlike random/verse this doesn't need a curated
        pool.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: book
          in: query
          description: Limit to one book — USFM/name/id.
          schema: { type: string, example: "Psalms" }
      responses:
        "200":
          description: The randomly chosen chapter's verses.
          content:
            application/json: { schema: { $ref: "#/components/schemas/Passage" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/verse-of-the-day:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: getVerseOfTheDay
      summary: Deterministic verse of the day
      description: |
        The same verse for every caller on a given UTC calendar day, drawn
        from the same curated popular-verse pool
        `GET /bibles/{version}/random/verse` uses — unlike that endpoint,
        this one is deterministic (date-seeded, not per-request random), so a
        "verse of the day" widget shows every visitor the same thing.
        Cacheable at the edge (`Cache-Control`) until the next UTC midnight.
      parameters:
        - $ref: "#/components/parameters/Version"
      responses:
        "200":
          description: Today's verse.
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string }
                  date: { type: string, example: "2026-08-14" }
                  data: { $ref: "#/components/schemas/SearchResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/reading-plan:
    get:
      tags: [Bibles · Reading & discovery]
      operationId: getReadingPlan
      summary: A day-by-day reading schedule
      description: |
        One endpoint with three modes: general (canon/testament/book pacing, the
        default), topic (`topic=` set), and M'Cheyne (`plan=mcheyne`, a fixed
        365-day historical calendar). General-mode pacing and reading-time use
        this translation's own real per-chapter word counts, not a flat
        per-version-agnostic guess. `format=ical|csv|pdf` returns the same
        schedule as a downloadable file instead of JSON. `format=pdf` renders
        a fixed-size calendar grid; a day whose reading is too dense for one
        cell (e.g. `days=1` over the whole Bible) returns 400 `pdf_too_dense`
        rather than hiding part of that day's reading or resizing the grid.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: days
          in: query
          description: Days to spread the plan over (general/topic modes only).
          schema: { type: integer, minimum: 1, maximum: 730, default: 365 }
        - name: start_date
          in: query
          schema: { type: string, format: date }
        - name: testament
          in: query
          description: >-
            Restrict to one testament. General mode: ignored if `books` is set. Topic mode:
            filters the topic's own citations to that testament.
          schema: { type: string, enum: [ot, old, nt, new] }
        - name: books
          in: query
          description: Comma-separated USFM/name/id list to restrict to specific books (general mode). Takes precedence over `testament`.
          schema: { type: string, example: "MAT,MRK,LUK,JHN" }
        - name: days_of_week
          in: query
          description: Comma-separated weekday abbreviations to schedule on (e.g. skip weekends).
          schema: { type: string, example: "mon,tue,wed,thu,fri" }
        - name: age
          in: query
          description: Reader's age, feeds the reading_time estimate (general mode).
          schema: { type: integer, default: 18 }
        - name: include_psalms
          in: query
          description: Append a daily Psalm (cycling 1-150) to each day (general mode).
          schema: { type: string, enum: ["1"] }
        - name: include_proverbs
          in: query
          description: Append a daily Proverb (cycling 1-31) to each day (general mode).
          schema: { type: string, enum: ["1"] }
        - name: topic
          in: query
          description: Topic name — switches to topic mode.
          schema: { type: string, example: "faith" }
        - name: plan
          in: query
          description: Set to `mcheyne` to switch to M'Cheyne's fixed 365-day calendar.
          schema: { type: string, enum: [mcheyne] }
        - name: format
          in: query
          description: Response format. ical/csv/pdf are downloads generated from the same schedule, not stored files.
          schema: { type: string, enum: [json, ical, csv, pdf], default: json }
        - name: title
          in: query
          description: format=pdf only. Consumer branding — prepended to the auto-generated cover heading, e.g. "Grace Community Church eng_acv Reading Plan — August 2026". Max 100 characters.
          schema: { type: string, maxLength: 100, example: "Grace Community Church" }
        - name: url
          in: query
          description: format=pdf only. Consumer branding — rendered as a clickable link in the footer of every page (a scheme is added automatically if omitted). Max 200 characters.
          schema: { type: string, maxLength: 200, example: "yourapp.com" }
      responses:
        "200":
          description: The reading plan (shape depends on mode — see docs/api/DOCS.md), or a downloadable file when format is ical/csv/pdf.
          content:
            application/json: { schema: { type: object } }
            text/calendar: { schema: { type: string } }
            text/csv: { schema: { type: string } }
            application/pdf: { schema: { type: string, format: binary } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /bibles/{version}/verses:
    get:
      tags: [Bibles · Text & verses]
      operationId: getBatchVerses
      summary: Batch-fetch a scattered list of individual verses
      description: |
        Fetches a scattered (non-contiguous) list of individual verses in one
        call — a reading plan, a topical study, a sermon's proof-texts.
        Complements, doesn't duplicate, the chapter/verse endpoint's own
        `start-end` range syntax, which is for a *contiguous* run of verses
        within a single book/chapter.

        Each ref is `BOOK.CHAPTER.VERSE` (book may be a USFM code, English
        name, or local id), comma-separated, e.g. `JHN.3.16,GEN.1.1`. A ref
        that fails to parse, doesn't resolve to a book in this version, or
        doesn't exist as a stored verse is reported in `not_found` instead of
        failing the whole call. `data` follows the input refs' order; capped
        at 200 refs per call.
      parameters:
        - $ref: "#/components/parameters/Version"
        - name: refs
          in: query
          required: true
          description: Comma-separated BOOK.CHAPTER.VERSE list, e.g. "JHN.3.16,GEN.1.1".
          schema: { type: string, example: "JHN.3.16,GEN.1.1,PSA.23.1" }
      responses:
        "200":
          description: The resolved verses, plus any refs that didn't resolve.
          content:
            application/json: { schema: { $ref: "#/components/schemas/BatchVersesResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /reading-time:
    get:
      tags: [Meta · Textual notes & tools]
      operationId: getReadingTime
      summary: Estimate reading time for a word count, by age
      description: |
        Not tied to any translation — pass the word
        count of whatever text you're timing. The same age-speed curve also
        drives the reading_time block on GET /bibles/{version}/reading-plan.
      parameters:
        - name: words
          in: query
          required: true
          schema: { type: integer, minimum: 1 }
        - name: age
          in: query
          schema: { type: integer, default: 18 }
      responses:
        "200":
          description: Reading-time estimate.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /chronology:
    get:
      tags: [Meta · People & history]
      operationId: getChronology
      summary: Curated timeline of biblical events with dates and citations
      description: |
        A curated timeline of biblical and closely-related historical events
        with traditional dates and scripture citations (substantially
        expanded from an earlier, shorter 63-entry version that had real
        transcription errors, corrected here). Dates follow the
        Ussher chronological tradition throughout — one traditional
        reference system among several a scholar could defend, not modern
        academic consensus. Dates prefixed "c." are derived from a stated
        in-text interval rather than a directly attested year, or fall in
        the Judges period, whose internal chronology is a well-known open
        question even among traditional chronologists. `reference` is a
        free-text citation string (empty for a few intertestamental entries
        with no scripture reference at all) — pipe it through
        GET /parse/citation if you need it resolved to a structured
        book/chapter/verse.
      responses:
        "200":
          description: The full curated timeline, ordered earliest first.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ChronologyResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /books/abbreviations:
    get:
      tags: [Books]
      operationId: listBookAbbreviations
      summary: List every book's known abbreviations
      description: >-
        The full abbreviation table, grouped by book. Takes no parameters —
        always returns every entry.
      responses:
        "200":
          description: Every book's abbreviations.
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer }
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/BookAbbreviationGroup" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /books/{book}:
    get:
      tags: [Books]
      operationId: getBookIdentity
      summary: Resolve a book reference to its identity
      description: >-
        Resolves a master book id, USFM code, English name, or abbreviation to its
        canonical identity — a single lookup, since id/code/name are always
        resolved together anyway.
      parameters:
        - $ref: "#/components/parameters/MasterBook"
      responses:
        "200":
          description: The book's identity.
          content:
            application/json: { schema: { $ref: "#/components/schemas/BookIdentityResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /books/{book}/info:
    get:
      tags: [Books]
      operationId: getBookInfo
      summary: Reference content for a book
      description: >-
        Introduction, authorship, themes, and similar reference content for a book.
        Version-independent — not nested under a translation. Not every book has content
        yet: a `404 book_info_not_available` (distinct from `book_not_found`) means the
        book exists but has no reference content yet.
      parameters:
        - $ref: "#/components/parameters/MasterBook"
      responses:
        "200":
          description: The book's reference content.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /books/{book}/commentaries:
    get:
      tags: [Books]
      operationId: getBookCommentaries
      summary: Commentaries covering a book
      description: >-
        The reverse of `GET /commentaries`'s own `books` field (#199): given a
        book, which commentary sources actually cover it. A source with an
        empty `books` list there (Gill, Matthew Henry) covers the whole Bible
        and is always included; every other source is included only when the
        requested book is in its own `books` list.
      parameters:
        - $ref: "#/components/parameters/MasterBook"
      responses:
        "200":
          description: Commentary sources covering the book.
          content:
            application/json: { schema: { $ref: "#/components/schemas/BookCommentariesResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /audio:
    get:
      tags: [Audio narrations]
      operationId: listAudioNarrations
      summary: List available audio narration sets
      description: |
        Discovery endpoint — lists every active narration set without
        minting anything, so a caller doesn't have to trial-and-error the mint
        route to find out what has audio. `audio_id` is the value the mint
        route's `version` path parameter expects; it's the same as `version_id`
        for a version's first/only narration, and gets a numeral suffix
        (`arb_svd_2`, ...) for an additional narration of the same text version.
      parameters:
        - name: version_id
          in: query
          description: >-
            Narrow to narrations of one text version — omit to list every
            active narration set. No example value on purpose: RapidAPI's
            playground pre-fills any example given here, which would silently
            narrow the default "try it" call to a single version instead of
            showing the full catalog.
          schema: { type: string }
      responses:
        "200":
          description: Active narration sets.
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer }
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AudioNarration" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /audio/{version}/{book}/{chapter}:
    get:
      tags: [Audio narrations]
      operationId: getAudioNarration
      summary: Get a signed URL for a chapter narration
      description: |
        Returns a **signed, time-limited** URL (`exp` + `sig`): it plays for
        `expires_in` seconds then stops resolving, so it cannot be shared or
        hotlinked durably. Call again for a fresh one.

        One **successful** call consumes one audio stream from your monthly
        allowance *and* one request from your quota. Failed mints consume
        nothing. Playing or seeking within a URL you already hold is free.

        Narrations are delivered from a CDN edge (`media.iqbible.com`) that
        verifies the signature itself, so an unsigned or expired request never
        reaches the file.

        RapidAPI subscribers are metered per consumer against the tier held
        there — Basic 100, Pro 5,000, Ultra 25,000, Mega 100,000 streams per
        month — and marketplace audio is a hard cap on every tier.
      parameters:
        - name: version
          in: path
          required: true
          description: >-
            A narration set's audio_id (see GET /audio) — not a text version
            code, though it's the same string for a version's first/only
            narration.
          schema: { type: string, example: "eng_kjv" }
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
      responses:
        "200":
          description: A playable signed URL.
          headers:
            X-RateLimit-Limit-Audio:
              description: Monthly stream allowance.
              schema: { type: integer }
            X-RateLimit-Remaining-Audio:
              description: Streams left this month.
              schema: { type: integer }
            X-RateLimit-Reset-Audio:
              description: Unix time when the allowance resets.
              schema: { type: integer }
            X-RateLimit-Overage-Audio:
              description: Streams served past the allowance this month (billable). Sent only while in overage.
              schema: { type: integer }
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string, example: "eng_kjv" }
                  book_usfm: { type: string, example: "JHN" }
                  chapter: { type: integer, example: 3 }
                  file:
                    type: string
                    format: uri
                    description: Signed URL. Streams `audio/mpeg` and supports HTTP range requests.
                  expires_in:
                    type: integer
                    description: Seconds until the signature expires.
                    example: 600
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: |
            Unknown version, book or chapter — or `audio_not_available` when the
            caller's plan includes no audio at all.
          content:
            application/json: { schema: { $ref: "#/components/schemas/Error" } }
        "429":
          description: |
            Rate limit, request quota, or the audio allowance
            (`audio_quota_exceeded` on a hard-capped plan — which includes every
            RapidAPI tier — or `audio_overage_limit_reached` at the overage
            ceiling on a paid direct plan). Honor `Retry-After`.
          headers:
            Retry-After:
              description: Seconds until the allowance resets.
              schema: { type: integer }
            X-RateLimit-Limit-Audio:
              description: Monthly stream allowance.
              schema: { type: integer }
            X-RateLimit-Remaining-Audio:
              description: Streams left this month (0 when blocked).
              schema: { type: integer }
            X-RateLimit-Reset-Audio:
              description: Unix time when the allowance resets.
              schema: { type: integer }
          content:
            application/json: { schema: { $ref: "#/components/schemas/Error" } }

  /extrabiblical:
    get:
      tags: [Extrabiblical works]
      operationId: listExtrabiblical
      summary: List extrabiblical works
      description: Work ids look like `extra_eng_1_enoch`. Hyphens are accepted and treated as underscores.
      responses:
        "200":
          description: Available works.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /extrabiblical/{book}/meta:
    get:
      tags: [Extrabiblical works]
      operationId: getExtrabiblicalMeta
      summary: Chapter and verse counts for a work
      description: Retrieve the chapter and verse structure of a non-canonical work without fetching full text.
      parameters:
        - $ref: "#/components/parameters/ExtraBook"
      responses:
        "200":
          description: Chapters with verse counts.
          content:
            application/json: { schema: { type: object } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /extrabiblical/{book}/info:
    get:
      tags: [Extrabiblical works]
      operationId: getExtrabiblicalWorkInfo
      summary: Reference content for an extrabiblical work
      description: >-
        Introduction, authorship, and dating for the work itself. Same
        `404 book_info_not_available` behavior as `GET /books/{book}/info` for
        works that don't have content yet.
      parameters:
        - $ref: "#/components/parameters/ExtraBook"
      responses:
        "200":
          description: The work's reference content.
          content:
            application/json: { schema: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /extrabiblical/{book}/{chapter}:
    get:
      tags: [Extrabiblical works]
      operationId: getExtrabiblicalChapter
      summary: Read a chapter from a work
      description: Fetch all verses in a chapter from a non-canonical text (apocryphal, pseudepigraphal, etc.).
      parameters:
        - $ref: "#/components/parameters/ExtraBook"
        - name: chapter
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
      responses:
        "200":
          description: The chapter's verses.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ExtraPassage" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /extrabiblical/{book}/{chapter}/{verse}:
    get:
      tags: [Extrabiblical works]
      operationId: getExtrabiblicalVerse
      summary: Read a verse or range from a work
      description: Fetch a single verse or verse range from a non-canonical text.
      parameters:
        - $ref: "#/components/parameters/ExtraBook"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: string, example: "1" }
      responses:
        "200":
          description: The requested verses.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ExtraPassage" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/greek/strongs/{key}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getGreekLexiconEntry
      summary: Greek Strong's lexicon entry
      description: |
        Look up a Greek word by its Strong's concordance number with definition
        and usage information. Lives under an explicit /strongs/ segment
        (previously /lexicon/greek/{strongs}) to match the source-prefixed
        shape every other lexicon source uses — see /lexicon/sources.
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string, example: "G26" }
      responses:
        "200":
          description: The lexicon entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LexiconEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/hebrew/strongs/{key}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getHebrewLexiconEntry
      summary: Hebrew Strong's lexicon entry
      description: |
        Lives under an explicit /strongs/ segment (previously
        /lexicon/hebrew/{strongs}) to match the source-prefixed shape every
        other lexicon source uses — see /lexicon/sources.
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string, example: "H430" }
      responses:
        "200":
          description: The lexicon entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LexiconEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/greek/characters:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getGreekCharacters
      summary: Greek alphabet
      description: |
        The 24-letter Greek alphabet, lowercase and uppercase, each with its
        Unicode code point. Static reference data, not version- or
        Strong's-scoped — always available, no study dataset required.
      responses:
        "200":
          description: The Greek alphabet.
          content:
            application/json: { schema: { $ref: "#/components/schemas/GreekCharactersResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/hebrew/characters:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getHebrewCharacters
      summary: Hebrew characters
      description: |
        The full Hebrew character set — the 22-letter alphabet, vowel points,
        cantillation accents, punctuation, scribal marks, signs, and Yiddish
        ligatures — each with its Unicode code point. Static reference data,
        same availability as the Greek alphabet endpoint. Five letters (kaf,
        mem, nun, pe, tsadi) carry an extra `final`/`final_unicode` pair for
        their word-final ("sofit") form.
      responses:
        "200":
          description: The Hebrew character set.
          content:
            application/json: { schema: { $ref: "#/components/schemas/HebrewCharactersResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/hebrew/bdb/{key}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getBdbLexiconEntry
      summary: Hebrew lexicon entry (Brown-Driver-Briggs, enhanced)
      description: |
        Look up a Hebrew word in the enhanced Brown-Driver-Briggs lexicon by
        its own native BDB code (not a Strong's number — see
        /lexicon/crosswalk/{strongs} to map from Strong's to a BDB code).
        First of several planned lexicon sources beyond Strong's — see
        /lexicon/sources for the full catalog.
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string, example: "BDB1005" }
      responses:
        "200":
          description: The lexicon entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/BdbEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/greek/lsj/{key}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getLsjLexiconEntry
      summary: Greek lexicon entry (Liddell-Scott-Jones, full)
      description: |
        Look up a Greek word in the full Liddell-Scott-Jones lexicon by its
        own native disambiguated Strong's-based code (dStrong, e.g.
        "G0001G" — not a plain Strong's number; see
        /lexicon/crosswalk/{strongs} to map from Strong's to this key).
        Second lexicon source beyond Strong's, and the first Greek one — see
        /lexicon/sources for the full catalog.
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string, example: "G0026" }
      responses:
        "200":
          description: The lexicon entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LsjEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/greek/lsj/word/{greek}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getLsjLexiconEntryByWord
      summary: Greek lexicon entry (Liddell-Scott-Jones, full) by headword
      description: |
        Look up a Greek word in the full Liddell-Scott-Jones lexicon by its
        actual accented headword (e.g. "ἀγάπη") instead of a Strong's-shaped
        key. Exact match, diacritics included — an unaccented or differently
        cased spelling will not match. A small number of headwords (proper
        names shared by more than one person, e.g. multiple people named
        "Ἰωσήφ") return more than one entry.
      parameters:
        - name: greek
          in: path
          required: true
          schema: { type: string, example: "ἀγάπη" }
      responses:
        "200":
          description: The lexicon entry/entries.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LsjEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/greek/abbott-smith/{key}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getAbbottSmithLexiconEntry
      summary: Greek lexicon entry (Abbott-Smith)
      description: |
        Look up a Greek word in the Abbott-Smith Manual Greek Lexicon of the
        New Testament by its own native disambiguated Strong's-based code
        (dStrong, e.g. "G0026" — not necessarily a plain Strong's number; see
        /lexicon/crosswalk/{strongs} to map from Strong's to this key). Third
        lexicon source beyond Strong's, and the second Greek one (after LSJ)
        — see /lexicon/sources for the full catalog.
      parameters:
        - name: key
          in: path
          required: true
          schema: { type: string, example: "G0026" }
      responses:
        "200":
          description: The lexicon entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/AbbottSmithEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/crosswalk/{strongs}:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getLexiconCrosswalk
      summary: Lexicon crosswalk (Strong's number -> each source's native key)
      description: |
        Bridges a Strong's number to the native key used by every other
        lexicon source available for it (e.g. a BDB code) — each source
        keeps its own native keying scheme rather than being forced into a
        Strong's-shaped key, since not every source maps 1:1 to Strong's.
      parameters:
        - name: strongs
          in: path
          required: true
          schema: { type: string, example: "H430" }
      responses:
        "200":
          description: The crosswalk entries for this Strong's number.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LexiconCrosswalkEntry" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /lexicon/sources:
    get:
      tags: [Study · Lexicon (Greek/Hebrew)]
      operationId: getLexiconSources
      summary: Lexicon sources catalog
      description: Every lexicon source available under /lexicon, its language, native key type, route, and license.
      responses:
        "200":
          description: The lexicon source catalog.
          content:
            application/json: { schema: { $ref: "#/components/schemas/LexiconSourcesResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /original-language/{book}/{chapter}:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: getOriginalLanguageChapter
      summary: Original-language text for a chapter
      description: |
        The received Hebrew (OT) or Greek (NT) source text for every word in the
        chapter, with pronunciation, Strong's tagging, and the Strong's glossary
        joined in. Version-independent; scoped to the 66 canonical books.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
      responses:
        "200":
          description: The chapter's original-language words.
          content:
            application/json: { schema: { $ref: "#/components/schemas/OriginalLanguageResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /original-language/{book}/{chapter}/{verse}:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: getOriginalLanguageVerse
      summary: Original-language text for a verse
      description: |
        Same as the chapter form, restricted to a single verse.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
      responses:
        "200":
          description: The verse's original-language words.
          content:
            application/json: { schema: { $ref: "#/components/schemas/OriginalLanguageResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /original-language/search:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: searchOriginalLanguage
      summary: Every occurrence of a Strong's-tagged lemma across the whole canon
      description: |
        The reverse direction of the chapter/verse lookup above: given a
        Strong's number, every place that Hebrew/Greek word occurs in the
        received source text. The Strong's number's own `G`/`H` prefix
        selects the lexicon (Greek/Hebrew); no separate language parameter.
      parameters:
        - name: strongs
          in: query
          required: true
          description: A Strong's number, e.g. `G26` (Greek) or `H430` (Hebrew).
          schema: { type: string, example: "G26" }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500, default: 50 }
        - name: offset
          in: query
          schema: { type: integer, minimum: 0, default: 0 }
        - name: count
          in: query
          description: Set to `1` to include `total_matches` (a second query).
          schema: { type: string, enum: ["1"] }
      responses:
        "200":
          description: Every occurrence of the lemma, paginated.
          content:
            application/json: { schema: { $ref: "#/components/schemas/OriginalLanguageSearchResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /commentaries:
    get:
      tags: [Study · Commentaries & dictionaries]
      operationId: listCommentaries
      summary: List available commentary sources
      description: |
        Every available `{name}` value for the Commentary endpoints below —
        `gill` (John Gill's *Exposition of the Entire Bible*, 1746-63),
        `mhenry` (Matthew Henry's *Complete Commentary on the Whole Bible*,
        1706-21), plus 324 authors/sources from
        HistoricalChristianFaith/Commentaries-Database (Early Fathers through
        Reformation/Modern). `books` lists the USFM codes each source
        actually has commentary on — most of the HistoricalChristianFaith
        sources wrote on specific books, not the whole Bible, unlike Gill and
        Matthew Henry (both all 66).
      responses:
        "200":
          description: Available commentary sources.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CommentaryListResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /commentaries/{name}/{book}/{chapter}:
    get:
      tags: [Study · Commentaries & dictionaries]
      operationId: getCommentaryChapter
      summary: Commentary for a chapter (or a book/chapter introduction)
      description: |
        Verse-by-verse commentary text. Chapter 0 is that book's own
        introduction; on a real chapter, verse 0 is that chapter's
        introduction — both included alongside numbered verses in the
        chapter form.

        Optional `canon`: 404s if `{book}` isn't in the requested canon —
        several HistoricalChristianFaith sources do have entries on
        deuterocanonical books, so this has real effect today.

        Sends an `ETag`; send it back as `If-None-Match` to get a body-free
        `304 Not Modified` when the content hasn't changed since your last call.
      parameters:
        - $ref: "#/components/parameters/CommentaryName"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/CommentaryChapter"
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The chapter's commentary entries.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CommentaryResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /commentaries/{name}/{book}/{chapter}/{verse}:
    get:
      tags: [Study · Commentaries & dictionaries]
      operationId: getCommentaryVerse
      summary: Commentary for a single verse
      description: |
        Same as the chapter form, restricted to a single verse (or that
        chapter's introduction, if verse is 0). Sends an `ETag`; send it back
        as `If-None-Match` for a body-free `304 Not Modified` when unchanged.
      parameters:
        - $ref: "#/components/parameters/CommentaryName"
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/CommentaryChapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 0, example: 1 }
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The verse's commentary entry.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CommentaryResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /dictionaries:
    get:
      tags: [Study · Commentaries & dictionaries]
      operationId: listDictionaries
      summary: List available Bible dictionaries
      description: |
        Every available dictionary source — five public-domain 19th-century
        dictionaries as of this writing (Easton's, Smith's, Hastings',
        Hitchcock's, Schaff's), each independently sourced and parsed.
      responses:
        "200":
          description: Available dictionaries.
          content:
            application/json: { schema: { $ref: "#/components/schemas/DictionaryListResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /dictionaries/{dictionaryId}:
    get:
      tags: [Study · Commentaries & dictionaries]
      operationId: getDictionaryDefinition
      summary: Look up or search a term definition
      description: |
        An exact, case-insensitive term
        match (e.g. `?q=Aaron`) returns that single entry; otherwise `q` is run
        as a full-text search across every term and definition in that
        dictionary, best matches first.
      parameters:
        - $ref: "#/components/parameters/DictionaryId"
        - name: q
          in: query
          required: true
          description: Term or search phrase.
          schema: { type: string, example: "Aaron" }
        - name: match
          in: query
          description: |
            Search mode when q isn't an exact term match. `all` = every word
            required, `any` = any word, `phrase` = exact phrase, `prefix` =
            each word as a `word*` wildcard, `boolean` = raw MySQL
            boolean-mode query. Ignored for an exact term match.
          schema:
            type: string
            default: all
            enum: [all, any, phrase, prefix, boolean]
        - name: limit
          in: query
          description: Max results for the search fallback. Doesn't apply to an exact term match, which always returns exactly one entry.
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
      responses:
        "200":
          description: Matching definition(s).
          content:
            application/json: { schema: { $ref: "#/components/schemas/DictionaryDefinitionResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /cross-references/{book}/{chapter}:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: getCrossReferencesChapter
      summary: Cross-references for a chapter
      description: |
        Verse-to-verse cross-references for every verse in the chapter. Each
        entry's `reference` is a fully USFM-coded target passage, ready to use
        directly with `GET /bibles/{version}/{book}/{chapter}/{verse}`.

        Optional `canon`: guards `{book}` (404 if it's not in the requested
        canon) and drops any target reference outside it.

        Sends an `ETag`; send it back as `If-None-Match` to get a body-free
        `304 Not Modified` when the content hasn't changed since your last call.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The chapter's cross-references.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CrossReferenceResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /cross-references/graph:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: getCrossReferenceGraph
      summary: Cross-reference graph shape for visualization tooling
      description: |
        The same `cross_reference` data as the endpoints above, reshaped as
        `{nodes, edges}` instead of a flat per-verse list — built for the
        classic Chris-Harrison-style Bible cross-reference arc diagram and
        similar graph-visualization tooling. Aggregated at chapter
        granularity (not verse), and edge `weight` is a count of
        verse-level cross-reference rows between two chapters, not the
        underlying table's own per-row `weight` column.

        Walks outward from `book`/`chapter` breadth-first, `depth` hops
        (default 1, max 3). The response is capped at 250 nodes; if the walk
        would exceed that, it stops early and sets `truncated: true` rather
        than growing unbounded.

        Sends an `ETag`; send it back as `If-None-Match` to get a body-free
        `304 Not Modified` when the content hasn't changed since your last call.
      parameters:
        - name: book
          in: query
          required: true
          description: USFM code, English name, or local id.
          schema: { type: string, example: "GEN" }
        - name: chapter
          in: query
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
        - name: depth
          in: query
          description: BFS hops outward from the starting chapter.
          schema: { type: integer, minimum: 1, maximum: 3, default: 1 }
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The cross-reference graph rooted at that chapter.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CrossReferenceGraphResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /ai/context:
    get:
      tags: [Study · AI, parsing & embeds]
      operationId: getAIContext
      summary: LLM-ready bundle of verse text, cross-references, commentary, Strong's, and original-language text
      description: |
        Composes verse text with cross-references, commentary, Strong's
        glosses, and original-language text for one reference into a single
        prompt-ready response — the "LLM Context Wrapper". Pure composition
        over `GET /bibles/{version}/{book}/{chapter}/{verse}`,
        `GET /cross-references/...`, `GET /commentaries/...`, and
        `GET /original-language/...` — no new data.

        Unlike every other endpoint here, the reference is a single `ref`
        query parameter (`BOOK.chapter.verse`, e.g. `JHN.3.16`) rather than
        path segments — the natural shape for an LLM/agent caller.

        A requested `include=` section with no data for this verse is simply
        omitted from the response, not an error; a section that fails for a
        real reason (a backend error) is reported in `errors` instead of
        failing sections that succeeded.

        Monthly-quota cost is dynamic: 1 for the base verse text plus 1 per
        valid `include=` value requested, charged against the declared list
        before any query runs (see the `X-RateLimit-Cost-Month` response
        header).
      parameters:
        - name: ref
          in: query
          required: true
          schema: { type: string, example: "JHN.3.16" }
          description: 'BOOK.chapter.verse — BOOK is a USFM code or English name with no "." in it.'
        - name: include
          in: query
          required: false
          schema: { type: string, example: "crossrefs,commentary,strongs,original" }
          description: "Comma-separated. Valid values: crossrefs, commentary, strongs, original."
        - name: version
          in: query
          required: false
          schema: { type: string, default: "eng_kjv", example: "eng_kjv" }
          description: Bible version code the base verse text comes from — the one section here that's version-specific.
        - name: commentary
          in: query
          required: false
          schema: { type: string, default: "gill", example: "gill" }
          description: Commentary source name (see GET /commentaries). Defaults to gill; gill and mhenry are the only sources with full 66-book coverage.
        - name: format
          in: query
          required: false
          schema: { type: string, enum: [json, text], default: json }
          description: text returns a flattened plain-text block ready for direct prompt injection instead of JSON.
      responses:
        "200":
          description: The assembled context.
          headers:
            X-RateLimit-Cost-Month:
              description: Monthly-quota units this call charged (1 + number of valid include values).
              schema: { type: integer }
          content:
            application/json:
              schema:
                type: object
                properties:
                  ref: { type: string, example: "JHN.3.16" }
                  book: { type: string, example: "JHN" }
                  name_en: { type: string, example: "John" }
                  chapter: { type: integer, example: 3 }
                  verse: { type: integer, example: 16 }
                  version: { type: string, example: "eng_kjv" }
                  text: { type: string, example: "For God so loved the world..." }
                  cross_references:
                    type: array
                    items:
                      type: object
                      properties:
                        verse: { type: integer }
                        weight: { type: integer }
                        reference:
                          type: object
                          properties:
                            book: { type: string, example: "ROM" }
                            chapter: { type: integer }
                            verse: { type: integer }
                  commentary_source: { type: string, example: "gill" }
                  commentary:
                    type: array
                    items:
                      type: object
                      properties:
                        chapter: { type: integer }
                        verse: { type: integer }
                        text: { type: string }
                        source_title: { type: string }
                  strongs:
                    type: array
                    items:
                      type: object
                      properties:
                        strongs_id: { type: string, example: "G3779" }
                        glossary: { type: string, example: "thus, in this way" }
                  language: { type: string, example: "greek" }
                  original:
                    type: array
                    items:
                      type: object
                      properties:
                        verse: { type: integer }
                        word: { type: string }
                        strongs_id: { type: string }
                        morph: { type: string }
                        glossary: { type: string }
                  errors:
                    type: object
                    additionalProperties: { type: string }
                    description: Set only for a requested include= section that genuinely failed.
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /embed/verse:
    get:
      tags: [Growth]
      operationId: getEmbedVerse
      summary: Embeddable HTML verse widget (public, no API key)
      description: |
        A small, self-contained HTML snippet suitable for an `<iframe>`
        embed — a verse reference, its text, and a credit link back to IQ
        Bible. Public and unauthenticated by design: the point is that a
        third party can embed this on their own site with zero signup
        friction, so requiring a key here would defeat the feature.

        Rate-limited per IP instead of per subscriber (no `X-RateLimit-*`
        headers — there's no subscriber identity on an unauthenticated
        route), plus a small global concurrency ceiling shared with
        `GET /image/verse`, so free/public traffic can never exhaust the
        database connection pool paid traffic also depends on.
      parameters:
        - name: ref
          in: query
          required: true
          schema: { type: string, example: "JHN.3.16" }
          description: 'BOOK.chapter.verse — BOOK is a USFM code or English name with no "." in it.'
        - name: version
          in: query
          required: false
          schema: { type: string, default: "eng_kjv", example: "eng_kjv" }
        - name: theme
          in: query
          required: false
          schema: { type: string, enum: [light, dark], default: light }
        - name: brand_name
          in: query
          required: false
          schema: { type: string, maxLength: 80, default: "IQBible.com" }
          description: White-labels the credit line with your own name instead of IQ Bible's.
        - name: brand_url
          in: query
          required: false
          schema: { type: string, format: uri, default: "https://iqbible.com" }
          description: White-labels the credit link's target. Must be an absolute http(s) URL.
      responses:
        "200":
          description: An HTML document containing the verse widget.
          content:
            text/html: { schema: { type: string } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /image/verse:
    get:
      tags: [Growth]
      operationId: getImageVerse
      summary: Shareable verse card image, SVG/PNG/JPEG (public, no API key)
      description: |
        A server-rendered "verse card" — reference, verse text, and an IQ
        Bible credit line — for devotional and social-sharing apps. `svg` is
        the default: crisp at any embed size and needs no rasterizing per
        request. `png`/`jpg` render the same card as a raster image, for
        social platforms that don't accept SVG uploads. Same
        public/unauthenticated, IP-plus-global-rate-limited treatment as
        `GET /embed/verse`, regardless of format.
      parameters:
        - name: ref
          in: query
          required: true
          schema: { type: string, example: "JHN.3.16" }
          description: 'BOOK.chapter.verse — BOOK is a USFM code or English name with no "." in it.'
        - name: version
          in: query
          required: false
          schema: { type: string, default: "eng_kjv", example: "eng_kjv" }
        - name: style
          in: query
          required: false
          schema: { type: string, enum: [light, dark], default: light }
        - name: format
          in: query
          required: false
          schema: { type: string, enum: [png, svg, jpg, jpeg], default: png }
          description: >-
            webp is not supported — no pure-Go WebP encoder avoids a cgo/libwebp
            dependency. Defaults to png — most social platforms (and RapidAPI's
            own endpoint-testing preview) can't render an SVG response inline.
            Pass format=svg for a crisp/scalable embed, or if you need brand_url's
            clickable credit link (SVG only — see brand_url below).
        - name: brand_name
          in: query
          required: false
          schema: { type: string, maxLength: 80, default: "IQBible.com" }
          description: White-labels the credit line with your own name instead of IQ Bible's.
        - name: brand_url
          in: query
          required: false
          schema: { type: string, format: uri, default: "https://iqbible.com" }
          description: White-labels the credit link's target. Must be an absolute http(s) URL. SVG only — a raster (png/jpg) card has no way to carry a live link.
      responses:
        "200":
          description: An SVG, PNG, or JPEG image, depending on `format`.
          content:
            image/svg+xml: { schema: { type: string, format: binary } }
            image/png: { schema: { type: string, format: binary } }
            image/jpeg: { schema: { type: string, format: binary } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /cross-references/{book}/{chapter}/{verse}:
    get:
      tags: [Study · Original language & cross-refs]
      operationId: getCrossReferencesVerse
      summary: Cross-references for a single verse
      description: |
        Same as the chapter form, restricted to a single verse. Sends an
        `ETag`; send it back as `If-None-Match` for a body-free
        `304 Not Modified` when unchanged.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
        - $ref: "#/components/parameters/CanonFilter"
      responses:
        "200":
          description: The verse's cross-references.
          content:
            application/json: { schema: { $ref: "#/components/schemas/CrossReferenceResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /icons/{book}:
    get:
      tags: [Study · Icons & illustrations]
      operationId: getBookIcon
      summary: A book's icon
      description: |
        One icon per Bible book, color or black-and-white.
        Version-independent — the icon belongs to the book, not a
        translation.
      parameters:
        - $ref: "#/components/parameters/Book"
        - name: style
          in: query
          required: false
          schema: { type: string, enum: [color, bw], default: color }
      responses:
        "200":
          description: The book's icon.
          content:
            application/json: { schema: { $ref: "#/components/schemas/IconResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /illustrations:
    get:
      tags: [Study · Icons & illustrations]
      operationId: listIllustrations
      summary: List available illustration artists/works
      description: |
        Every illustration work with staged images, keyed by the id used
        in the per-reference endpoint's `?artist=` filter (`dore`,
        `schnorr`, `sweet`). count/needs_review_count are both measured
        per logical image, not doubled for the two staged resolutions.
      responses:
        "200":
          description: Available illustration artists/works.
          content:
            application/json: { schema: { $ref: "#/components/schemas/IllustrationListResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /illustrations/{book}/{chapter}:
    get:
      tags: [Study · Icons & illustrations]
      operationId: getIllustrationsChapter
      summary: Illustrations for a chapter
      description: |
        Every staged illustration tagged anywhere in that chapter, across every
        artist/work. A verse-range image (e.g. one tagged "Gen. 1:26-27") is
        included here regardless of which verse in its range it's tagged to.
      parameters:
        - name: book
          in: path
          required: true
          description: USFM code, English name, or the book's local id within that version.
          schema: { type: string, example: "GEN" }
        - name: chapter
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
        - name: size
          in: query
          required: false
          description: Which staged resolution's url to return. Native source resolutions, not normalized to a common size.
          schema: { type: string, enum: [high, medium], default: high }
        - name: artist
          in: query
          required: false
          description: Restrict to one artist's illustrations. Leave blank for every artist.
          schema: { type: string, enum: [dore, schnorr, sweet] }
      responses:
        "200":
          description: The chapter's illustrations.
          content:
            application/json: { schema: { $ref: "#/components/schemas/IllustrationResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /illustrations/{book}/{chapter}/{verse}:
    get:
      tags: [Study · Icons & illustrations]
      operationId: getIllustrationsVerse
      summary: Illustrations for a single verse
      description: >-
        Same as the chapter form, restricted to illustrations tagged to (or
        ranging over) a single verse — verse is a path segment here and always
        required. If you don't have a specific verse, use
        `GET /illustrations/{book}/{chapter}` instead.
      parameters:
        - name: book
          in: path
          required: true
          description: USFM code, English name, or the book's local id within that version.
          schema: { type: string, example: "GEN" }
        - name: chapter
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 26 }
        - name: size
          in: query
          required: false
          schema: { type: string, enum: [high, medium], default: high }
        - name: artist
          in: query
          required: false
          description: Restrict to one artist's illustrations. Leave blank for every artist.
          schema: { type: string, enum: [dore, schnorr, sweet] }
      responses:
        "200":
          description: The verse's illustrations.
          content:
            application/json: { schema: { $ref: "#/components/schemas/IllustrationResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /geo/{book}/{chapter}:
    get:
      tags: [Study · Geography]
      operationId: getGeoChapter
      summary: Places mentioned in a chapter
      description: |
        Every Bible place mentioned anywhere in the chapter, with map
        coordinates. A place unresolvable to a real-world location carries
        `special` instead of lat/lon/confidence_score, never a fabricated
        coordinate.
      parameters:
        - $ref: "#/components/parameters/Book"
        - $ref: "#/components/parameters/Chapter"
      responses:
        "200":
          description: The chapter's places.
          content:
            application/json: { schema: { $ref: "#/components/schemas/GeoReferenceResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /geo/{book}/{chapter}/{verse}:
    get:
      tags: [Study · Geography]
      operationId: getGeoVerse
      summary: Places mentioned in a single verse
      description: >-
        Same as the chapter form, restricted to a single verse — verse is a path
        segment here and always required. If you don't have a specific verse,
        use `GET /geo/{book}/{chapter}` instead.
      parameters:
        - name: book
          in: path
          required: true
          description: USFM code, English name, or the book's local id within that version.
          schema: { type: string, example: "EXO" }
        - name: chapter
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
        - name: verse
          in: path
          required: true
          schema: { type: integer, minimum: 1, example: 1 }
      responses:
        "200":
          description: The verse's places.
          content:
            application/json: { schema: { $ref: "#/components/schemas/GeoReferenceResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /geo/places:
    get:
      tags: [Study · Geography]
      operationId: listGeoPlaces
      summary: Search/browse the place gazetteer
      description: |
        Search/browse bible_places. All filters are optional and combine
        with AND.
      parameters:
        - name: q
          in: query
          required: false
          description: Case-insensitive substring match on the place's name.
          schema: { type: string, example: "jerusalem" }
        - name: type
          in: query
          required: false
          description: >-
            Exact match on place_type (e.g. settlement, river, mountain, region —
            open-ended, not a fixed enum). Combines with any other filter as AND —
            leave blank unless you want to narrow the q= match further.
          schema: { type: string }
        - name: book
          in: query
          required: false
          description: >-
            Only places with at least one verse occurrence in this book (USFM code,
            English name, or local id). Combines with any other filter as AND —
            leave blank unless you want to narrow the q= match further.
          schema: { type: string }
      responses:
        "200":
          description: Matching places.
          content:
            application/json: { schema: { $ref: "#/components/schemas/GeoPlacesListResponse" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /geo/places/{id}:
    get:
      tags: [Study · Geography]
      operationId: getGeoPlace
      summary: One place's full detail
      description: |
        One place's full detail, including its complete verse-occurrence
        list. {id} resolves by numeric bible_places id, by slug (same
        dual-resolution convention as GET /books/{book}), or by an exact,
        case-insensitive name if that name is unambiguous (an ambiguous name
        still 404s — use ?q= on GET /geo/places to disambiguate).
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric bible_places id, slug, or unambiguous exact name.
          schema: { type: string, example: "abana" }
      responses:
        "200":
          description: The place's full detail.
          content:
            application/json: { schema: { $ref: "#/components/schemas/GeoPlaceDetail" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /parse/citation:
    get:
      tags: [Study · AI, parsing & embeds]
      operationId: parseCitation
      summary: Parse a free-text citation
      description: |
        Parses one book+chapter citation (e.g. "Matt.1:2,3,7,10-14") into a
        resolved book/chapter/verse-list. Book text may be a USFM code, full
        English name, or common abbreviation (covers every canon this API
        tracks, not just the 66-book Protestant canon). Chapter/verse
        existence is not validated. Returns a plain book/chapter/verse-list —
        no composite verse-id output.
      parameters:
        - name: citation
          in: query
          required: true
          schema: { type: string, example: "Matt.1:2,3,7,10-14" }
        - name: version
          in: query
          required: false
          description: When given, also resolves each parsed verse's text for this version.
          schema: { type: string, example: "eng_kjv" }
      responses:
        "200":
          description: The parsed citation.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ParseCitationResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /parse/citations:
    get:
      tags: [Study · AI, parsing & embeds]
      operationId: scanCitations
      summary: Scan free text for every Scripture reference in it
      description: |
        The free-text generalization of `GET /parse/citation`: scans
        arbitrary prose (a sermon draft, an LLM completion, a transcript)
        for every Scripture reference embedded in it, resolved to
        USFM/book/chapter/verse — built for RAG/citation-checking use
        cases. Detection is heuristic (several book names/abbreviations are
        also ordinary English words, e.g. "Mark", "Acts", "Job"), so a
        false-positive match on non-Scripture prose is possible. A
        comma-separated list within one reference (e.g. "Rom.8:28,30-31")
        is split into one result per verse/range. GET, not POST, despite
        the free-text body — every public IQ Bible endpoint is GET.
      parameters:
        - name: text
          in: query
          required: true
          description: Free text to scan, up to 4,000 characters.
          schema: { type: string, example: "As it says in John 3:16, and see Romans 8:28." }
        - name: hydrate
          in: query
          required: false
          description: When true, also fetches each match's actual verse text.
          schema: { type: boolean, default: false }
        - name: version
          in: query
          required: false
          description: Which version to hydrate from. Defaults to eng_kjv when hydrate=true is set without it.
          schema: { type: string, example: "eng_kjv" }
      responses:
        "200":
          description: Every Scripture reference found in the text.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ScanCitationsResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-IQBible-API-Key
      description: Preferred. The legacy `X-API-Key` header is still accepted.
    BearerToken:
      type: http
      scheme: bearer
      description: "`Authorization: Bearer iqb_live_…`"
    ApiKeyQuery:
      type: apiKey
      in: query
      name: apikey
      description: Convenient for quick testing; prefer a header in production.

  parameters:
    Version:
      name: version
      in: path
      required: true
      description: Translation code, case-sensitive. List them with `GET /bibles`.
      schema:
        type: string
        pattern: "^[A-Za-z0-9_]+$"
        example: "eng_kjv"
    Book:
      name: book
      in: path
      required: true
      description: USFM code, English name, or the book's local id within that version.
      schema: { type: string, example: "JHN" }
    ExtraBook:
      name: book
      in: path
      required: true
      description: An extrabiblical work id from `GET /extrabiblical`.
      schema:
        type: string
        pattern: "^extra_[a-z0-9_]+$"
        example: "extra_eng_1_enoch"
    MasterBook:
      name: book
      in: path
      required: true
      description: >-
        USFM code, English name, or the version-independent master book id — distinct
        from a translation's own local book id.
      schema: { type: string, example: "Genesis" }
    Chapter:
      name: chapter
      in: path
      required: true
      schema: { type: integer, minimum: 1, example: 3 }
    CanonFilter:
      name: canon
      in: query
      required: false
      description: |
        Filter by Bible tradition, case-insensitive (e.g. `protestant`,
        `catholic`, `eastern orthodox`). Matched against each row's
        already-resolved canonical book, not a stored per-row tag, so a
        book outside the requested tradition drops out even though
        nothing on the row itself names a canon. Omitted returns
        everything, unfiltered. An unrecognized name returns 400.
      schema: { type: string }
    IncludeWordsOfJesus:
      name: include
      in: query
      description: |
        Set to `words_of_jesus` to attach each verse's red-letter segments
        (see `GET .../{verse}/words-of-jesus`) to this same response, instead
        of one extra request per verse. Omitted by default; unrecognized
        values are ignored.
      schema: { type: string, example: "words_of_jesus" }
    CommentaryChapter:
      name: chapter
      in: path
      required: true
      description: 0 = that book's own introduction.
      schema: { type: integer, minimum: 0, example: 1 }
    CommentaryName:
      name: name
      in: path
      required: true
      description: Commentary source — see GET /commentaries for the full list of 326 slugs.
      schema: { type: string, example: "gill" }
    DictionaryId:
      name: dictionaryId
      in: path
      required: true
      description: Dictionary source id — see GET /dictionaries for the full list.
      schema: { type: string, example: "easton" }

  schemas:
    Error:
      type: object
      description: |
        Every non-2xx response uses this envelope, so you can branch on the same
        top-level `status` field you use for success responses.
      properties:
        status: { type: string, example: "error" }
        error:
          type: object
          properties:
            status: { type: integer, example: 404 }
            code:
              type: string
              description: Stable machine-readable slug — branch on this, not `message`.
              example: "book_not_found"
            message: { type: string }
            detail:
              type: string
              description: The offending value or the underlying error.
            hint:
              type: string
              description: How to fix it.
            path: { type: string }
            method: { type: string }

    MeMeter:
      type: object
      description: One metered resource (requests or audio streams) for GET /me.
      properties:
        limit_per_minute:
          type: integer
          description: Requests only, omitted for audio. 0 = unlimited.
          example: 1200
        limit_per_month: { type: integer, example: 500000, description: "0 = unlimited, -1 = disabled (audio only)" }
        used: { type: integer, example: 12345 }
        remaining:
          type: integer
          nullable: true
          description: "`limit_per_month` minus `used`, floored at 0. `null` when unlimited or disabled."
          example: 487655
        overage_billable: { type: boolean }
        overage_per_1k_cents:
          type: integer
          description: Price per 1,000 past the monthly limit. Omitted (or 0) on a hard-capped plan.
        overage_used:
          type: integer
          description: Consumption past `limit_per_month` this period, once in overage.
        overage_cost_cents:
          type: integer
          description: Estimated overage charge so far this period.
        overage_ceiling:
          type: integer
          description: Absolute monthly ceiling (10x the plan quota) past which overage itself is refused with `429 overage_limit_reached`.

    Version:
      type: object
      properties:
        version_id: { type: string, example: "eng_kjv" }
        title: { type: string, example: "King James Version" }
        description: { type: string }
        license: { type: string, example: "Public Domain" }
        canon_id: { type: integer }
        language_code: { type: string, example: "eng" }
        language_name: { type: string, example: "English" }
        language_stage: { type: string, example: "Early Modern" }
        language_family: { type: string, example: "Indo-European" }
        audio_count:
          type: integer
          description: Active narration sets for this version — see GET /audio.
          example: 1
        words_of_jesus_count:
          type: integer
          description: Number of verses this version has word-level red-letter segmentation for (not a segment count — a verse can span several segments) — 0 means this version has no word-level red-letter data. See GET /bibles/{version}/{book}/{chapter}/{verse}/words-of-jesus.
          example: 2028

    AudioNarration:
      type: object
      description: >-
        source_url/license/notes are tracked internally but deliberately not
        returned here — see GET /credits and GET /licensing for sourcing and
        licensing instead. narrator is blank until a real narrator name is on
        file for that set (an import source like "wordproject.org" is not a
        narrator).
      properties:
        audio_id: { type: string, example: "eng_kjv" }
        version_id: { type: string, example: "eng_kjv" }
        narrator: { type: string, example: "" }
        published_at: { type: string, format: date, nullable: true }
        chapter_count: { type: integer, example: 1189 }
        active: { type: boolean }

    Verse:
      type: object
      properties:
        id: { type: string }
        book_number: { type: integer }
        chapter_number: { type: integer }
        verse_number: { type: integer }
        text: { type: string }
        has_words_of_jesus:
          type: boolean
          description: Only present when the chapter/verse request was made with `?include=words_of_jesus`.
        segments:
          type: array
          description: Only present when the chapter/verse request was made with `?include=words_of_jesus`.
          items: { $ref: "#/components/schemas/WordsOfJesusSegment" }

    WordsResponse:
      type: object
      properties:
        version: { type: string, example: "eng_kjv" }
        book: { type: string, example: "GEN" }
        chapter: { type: integer }
        verse: { type: integer }
        count: { type: integer }
        words:
          type: array
          items: { type: string }

    WordsOfJesusSegment:
      type: object
      properties:
        text: { type: string, example: "Allow it now, for this is the fitting way for us to fulfill all righteousness." }
        is_jesus: { type: boolean }

    WordsOfJesusResponse:
      type: object
      properties:
        version: { type: string, example: "eng_kjv" }
        book: { type: string, example: "MAT" }
        chapter: { type: integer }
        verse: { type: integer }
        text: { type: string }
        has_words_of_jesus: { type: boolean }
        segments:
          type: array
          items: { $ref: "#/components/schemas/WordsOfJesusSegment" }

    WordCountResponse:
      type: object
      properties:
        version: { type: string, example: "eng_kjv" }
        book: { type: string, example: "GEN" }
        chapter: { type: integer }
        verse: { type: integer }
        word_count: { type: integer }

    ChronologyEvent:
      type: object
      properties:
        event: { type: string, example: "The Exodus" }
        date: { type: string, example: "1491 B.C." }
        reference: { type: string, example: "Exodus 12:31-42" }

    ChronologyResponse:
      type: object
      properties:
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/ChronologyEvent" }

    GreekLetter:
      type: object
      properties:
        name: { type: string, example: "alpha" }
        lowercase: { type: string, example: "α" }
        lowercase_unicode: { type: string, example: "U+03B1" }
        uppercase: { type: string, example: "Α" }
        uppercase_unicode: { type: string, example: "U+0391" }

    GreekCharactersResponse:
      type: object
      properties:
        count: { type: integer, example: 24 }
        letters:
          type: array
          items: { $ref: "#/components/schemas/GreekLetter" }

    HebrewLetter:
      type: object
      properties:
        name: { type: string, example: "kaf" }
        character: { type: string, example: "כ" }
        unicode: { type: string, example: "U+05DB" }
        final: { type: string, example: "ך" }
        final_unicode: { type: string, example: "U+05DA" }

    HebrewSymbol:
      type: object
      properties:
        name: { type: string, example: "sheva" }
        character: { type: string, example: "ְ" }
        unicode: { type: string, example: "U+05B0" }

    HebrewCharactersResponse:
      type: object
      properties:
        letters:
          type: array
          items: { $ref: "#/components/schemas/HebrewLetter" }
        points:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }
        accents:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }
        punctuation:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }
        marks:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }
        signs:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }
        yiddish_ligatures:
          type: array
          items: { $ref: "#/components/schemas/HebrewSymbol" }

    ParseCitationResponse:
      type: object
      properties:
        citation: { type: string, example: "Matt.1:2,3,7,10-14" }
        book: { type: string, example: "MAT" }
        name_en: { type: string, example: "Matthew" }
        chapter: { type: integer }
        count: { type: integer }
        verses:
          type: array
          items: { type: integer }
        version:
          type: string
          description: Only present when a `?version=` was supplied.
        data:
          type: array
          description: Only present when a `?version=` was supplied. Non-existent verses are simply omitted.
          items: { $ref: "#/components/schemas/Verse" }

    BookAbbreviation:
      type: object
      properties:
        abbreviation: { type: string, example: "Matt" }
        is_primary: { type: boolean }

    BookIdentityResponse:
      type: object
      properties:
        master_id: { type: integer, example: 41 }
        usfm_code: { type: string, example: "MAT" }
        name_en: { type: string, example: "Matthew" }
        abbreviations:
          type: array
          description: Only present when the study DB is configured.
          items: { $ref: "#/components/schemas/BookAbbreviation" }

    BookAbbreviationGroup:
      type: object
      properties:
        master_id: { type: integer, example: 41 }
        usfm_code: { type: string, example: "MAT" }
        name_en: { type: string, example: "Matthew" }
        abbreviations:
          type: array
          items: { $ref: "#/components/schemas/BookAbbreviation" }

    Passage:
      type: object
      properties:
        version: { type: string }
        book: { type: string }
        chapter: { type: integer }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/Verse" }

    BatchVersesResponse:
      type: object
      properties:
        version: { type: string }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/Verse" }
        not_found:
          type: array
          description: Refs that didn't parse, resolve to a book, or exist as a stored verse.
          items: { type: string, example: "JHN.99.99" }

    ExtraPassage:
      type: object
      properties:
        type: { type: string, example: "extrabiblical" }
        book: { type: string, example: "extra_eng_1_enoch" }
        chapter: { type: integer }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              chapter: { type: integer }
              verse: { type: integer }
              text: { type: string }

    SearchResult:
      type: object
      properties:
        id: { type: integer }
        book_number: { type: integer }
        book_usfm: { type: string }
        chapter_number: { type: integer }
        verse_number: { type: integer }
        text: { type: string }
        highlight:
          type: string
          description: Present when `highlight=1`; matches wrapped in `<mark>`.
        score:
          type: number
          description: Present when `sort=relevance`.

    LexiconEntry:
      type: object
      properties:
        key: { type: string, example: "G26" }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              language: { type: string, example: "G" }
              glossary: { type: string }
              occurences:
                type: integer
                description: Spelling matches the API field as served.

    BdbEntry:
      type: object
      properties:
        key: { type: string, example: "BDB1005" }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              bdb_code: { type: string, example: "BDB1005" }
              head_word: { type: string, description: "Hebrew headword." }
              pos: { type: string, description: "Part of speech." }
              primary_gloss: { type: string }
              description: { type: string, description: "Full gloss/definition text." }
              senses:
                description: Structured sense/stem breakdown, this source's key enhancement over the base BDB text.
                type: array
                items: { type: object }

    LsjEntry:
      type: object
      properties:
        key: { type: string, example: "G0026" }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              native_key: { type: string, example: "G0026" }
              estrong: { type: string, description: "Base (non-disambiguated) Strong's-shaped number.", example: "G0026" }
              ustrong: { type: string, description: "STEPBible's cross-language 'unified' Strong's reference — may be a G or H code, or a combination expression." }
              relation:
                type: string
                description: How ustrong relates to estrong, when they differ (e.g. \"the Greek of\", \"a Name of\"). Empty for the common case.
              greek: { type: string }
              transliteration: { type: string }
              morph: { type: string }
              gloss: { type: string }
              meaning:
                type: string
                description: Full LSJ entry (HTML). Falls back to Abbott-Smith text for words with no classical-Greek LSJ entry, flagged in-line as such.

    AbbottSmithEntry:
      type: object
      properties:
        key: { type: string, example: "G0026" }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              native_key: { type: string, example: "G0026" }
              estrong: { type: string, description: "Base (non-disambiguated) Strong's-shaped number.", example: "G0026" }
              ustrong: { type: string, description: "STEPBible's cross-language 'unified' Strong's reference — may be a G or H code, or a combination expression." }
              relation:
                type: string
                description: How ustrong relates to estrong, when they differ (e.g. \"the Greek of\", \"a Name of\"). Empty for the common case.
              greek: { type: string }
              transliteration: { type: string }
              morph: { type: string }
              gloss: { type: string }
              meaning:
                type: string
                description: Abbott-Smith's definition (HTML), flagged in-line "(AS)". Falls back to Middle Liddell text for words with no Abbott-Smith entry, flagged "(ML)".

    LexiconCrosswalkEntry:
      type: object
      properties:
        key: { type: string, example: "H430" }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              strongs_id: { type: string, example: "H430" }
              source: { type: string, example: "bdb" }
              native_key: { type: string, example: "BDB65" }
              lemma: { type: string }

    LexiconSourcesResponse:
      type: object
      properties:
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              language: { type: string, example: "hebrew" }
              source: { type: string, example: "bdb" }
              name: { type: string, example: "Brown-Driver-Briggs (Enhanced)" }
              key_type: { type: string, example: "bdb_code" }
              example: { type: string, example: "BDB1005" }
              route: { type: string, example: "/api/v2/lexicon/hebrew/bdb/{key}" }
              license: { type: string }

    OriginalLanguageResponse:
      type: object
      properties:
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        chapter: { type: integer }
        verse:
          type: integer
          description: Omitted when the whole-chapter form was requested.
        language: { type: string, enum: [hebrew, greek] }
        count: { type: integer }
        words:
          type: array
          items:
            type: object
            properties:
              verse: { type: integer }
              word: { type: string, description: "The Hebrew or Greek word." }
              pronun:
                type: object
                description: IPA / SBL transliteration / anglicized pronunciation.
              strongs_id:
                type: string
                example: "H7225"
                description: Ready to use with GET /lexicon/hebrew|greek/{strongs}.
              morph: { type: string, example: "TH8804" }
              glossary: { type: string }
              order: { type: integer, description: Word order within the verse. }
              connected:
                type: boolean
                description: Grammatically connected to the adjacent word (e.g. a construct chain).
              parashah:
                type: integer
                description: Non-zero marks the start of a weekly Torah portion (Hebrew OT only).

    OriginalLanguageSearchResponse:
      type: object
      properties:
        strongs_id: { type: string, example: "G26" }
        language: { type: string, enum: [hebrew, greek] }
        glossary: { type: string }
        count: { type: integer, description: Occurrences in this page. }
        limit: { type: integer }
        offset: { type: integer }
        total_matches: { type: integer, description: "Only present when ?count=1 was passed." }
        occurrences:
          type: array
          items:
            type: object
            properties:
              book: { type: string, example: "JHN" }
              name_en: { type: string, example: "John" }
              chapter: { type: integer }
              verse: { type: integer }
              word: { type: string }
              morph: { type: string }
              order: { type: integer, description: Word order within the verse. }

    CommentaryResponse:
      type: object
      properties:
        name: { type: string, example: "gill" }
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        chapter: { type: integer }
        verse:
          type: integer
          description: Omitted when the whole-chapter form was requested.
        count: { type: integer }
        entries:
          type: array
          items:
            type: object
            properties:
              chapter: { type: integer }
              verse: { type: integer, description: "0 = that chapter's introduction." }
              chapter_end: { type: integer, description: "Set only when this entry spans a verse range." }
              verse_end: { type: integer, description: "Set only when this entry spans a verse range." }
              text: { type: string }
              source_title: { type: string, description: "The specific work/section this excerpt is drawn from, when the source distinguishes one (e.g. \"Commentary on Matthew\", or one of Henry's section headings). Omitted for Gill and for chapter/book-introduction entries." }
              citations:
                type: array
                description: >-
                  Scripture references auto-detected in `text`'s prose by the
                  same engine behind GET /parse/citations — additive; `text`
                  itself is never rewritten to carry this. Omitted when none
                  were found.
                items: { $ref: "#/components/schemas/CitationMatch" }

    CitationMatch:
      type: object
      description: >-
        One Scripture reference found by the citation-detection engine —
        shared by GET /parse/citations' `citations`, commentary entries'
        `citations`, and dictionary entries' `citations`.
      properties:
        raw: { type: string, example: "John 3:16" }
        book: { type: string, example: "JHN" }
        name_en: { type: string, example: "John" }
        chapter: { type: integer }
        verse: { type: integer }
        verse_end: { type: integer, description: "Set only when this match is a verse range." }
        start:
          type: integer
          description: >-
            Where `raw` begins within the scanned text, as a Unicode
            codepoint (rune) offset — NOT a byte offset. This is what
            `text.slice(start, end)` in JavaScript or `text[start:end]` in
            Python actually index by.
          example: 393
        end:
          type: integer
          description: Where `raw` ends within the scanned text (codepoint offset — see `start`).
          example: 401
        url:
          type: string
          description: >-
            The resolved reference's own API resource URL. A verse range has
            no dedicated range route, so this points at the range's starting
            verse.
          example: "/api/v2/bibles/eng_kjv/JOB/38/6"
        data:
          type: array
          description: Verse text — present only on GET /parse/citations with ?hydrate=true.
          items: { type: object }

    ScanCitationsResponse:
      type: object
      properties:
        count: { type: integer }
        version: { type: string, description: "Present only when ?hydrate=true was set.", example: "eng_kjv" }
        citations:
          type: array
          items: { $ref: "#/components/schemas/CitationMatch" }

    CommentaryListResponse:
      type: object
      properties:
        status: { type: string, example: "ok" }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/CommentarySource" }

    BookCommentariesResponse:
      type: object
      properties:
        status: { type: string, example: "ok" }
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/CommentarySource" }

    CommentarySource:
      type: object
      properties:
        name: { type: string, example: "thomas-aquinas" }
        author_name: { type: string, example: "Thomas Aquinas" }
        category: { type: string, example: "Western & Medieval Theology" }
        year: { type: string, example: "1274" }
        wiki_url: { type: string }
        license: { type: string }
        entry_count: { type: integer }
        book_count: { type: integer }
        books:
          type: array
          items: { type: string }
          description: USFM codes this source has commentary on.

    DictionarySource:
      type: object
      properties:
        id: { type: string, example: "easton" }
        name: { type: string, example: "Easton's Bible Dictionary" }
        author: { type: string, example: "Matthew George Easton" }
        year: { type: string, example: "1897" }
        tradition: { type: string, example: "Presbyterian" }
        license: { type: string, example: "Public domain" }
        entry_count: { type: integer }

    DictionaryListResponse:
      type: object
      properties:
        status: { type: string, example: "ok" }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/DictionarySource" }

    DictionaryEntry:
      type: object
      properties:
        term: { type: string, example: "Aaron" }
        slug: { type: string, example: "aaron" }
        definition: { type: string }
        scripture_refs:
          type: array
          description: The source dictionary's own citation strings, passed through as-is (not resolved to USFM/book/chapter/verse).
          items: { type: string, example: "Exodus 6:20" }
        citations:
          type: array
          description: >-
            Scripture references auto-detected in `definition`'s prose by the
            same engine behind GET /parse/citations — distinct from
            scripture_refs above (fully resolved, not passed-through
            strings). `definition` itself is never rewritten to carry this.
            Omitted when none were found.
          items: { $ref: "#/components/schemas/CitationMatch" }

    DictionaryDefinitionResponse:
      type: object
      properties:
        status: { type: string, example: "ok" }
        dictionary: { type: string, example: "easton" }
        query: { type: string, example: "Aaron" }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/DictionaryEntry" }

    CrossReferenceTarget:
      type: object
      description: >-
        The referenced passage, USFM-coded. book_end/chapter_end are present
        only when the range crosses a chapter or book boundary; verse_end is
        present whenever the target is a range.
      properties:
        book: { type: string, example: "PSA" }
        chapter: { type: integer }
        verse: { type: integer }
        book_end: { type: string }
        chapter_end: { type: integer }
        verse_end: { type: integer }

    CrossReferenceResponse:
      type: object
      properties:
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        chapter: { type: integer }
        verse:
          type: integer
          description: Omitted when the whole-chapter form was requested.
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              verse:
                type: integer
                description: The source verse this reference is from.
              weight:
                type: integer
                description: Source data's own per-reference ordering value.
              reference: { $ref: "#/components/schemas/CrossReferenceTarget" }

    CrossReferenceGraphResponse:
      type: object
      properties:
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        chapter: { type: integer }
        depth: { type: integer }
        node_count: { type: integer }
        edge_count: { type: integer }
        truncated: { type: boolean, description: Set when the walk hit the 250-node cap before exhausting depth. }
        nodes:
          type: array
          items:
            type: object
            properties:
              id: { type: string, example: "GEN.1", description: "The id edges reference (BOOK.CHAPTER)." }
              book: { type: string, example: "GEN" }
              name_en: { type: string, example: "Genesis" }
              chapter: { type: integer }
        edges:
          type: array
          items:
            type: object
            properties:
              source: { type: string, example: "GEN.1" }
              target: { type: string, example: "PSA.104" }
              weight: { type: integer, description: Count of verse-level cross-references between these two chapters. }

    IconResponse:
      type: object
      properties:
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        style: { type: string, enum: [color, bw], example: "color" }
        source: { type: string }
        url: { type: string, example: "https://media.iqbible.com/media/_images/icons/color/GEN.png" }

    IllustrationWork:
      type: object
      properties:
        id:
          type: string
          example: "dore"
          description: The exact value to pass as the per-reference endpoint's ?artist= filter.
        artist: { type: string }
        work_title: { type: string }
        source: { type: string }
        source_url: { type: string }
        license: { type: string, example: "Public domain" }
        count: { type: integer }
        needs_review_count:
          type: integer
          description: Images staged with a caption but no reference specific enough to resolve to a book — never returned by the per-reference endpoint.

    IllustrationListResponse:
      type: object
      properties:
        status: { type: string, example: "ok" }
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/IllustrationWork" }

    IllustrationTarget:
      type: object
      description: >-
        The illustration's tagged passage, USFM-coded. book_end/chapter_end are
        present only when the range crosses a chapter or book boundary; verse_end
        is present whenever the tag is a verse range.
      properties:
        book: { type: string, example: "GEN" }
        chapter: { type: integer }
        verse: { type: integer }
        book_end: { type: string }
        chapter_end: { type: integer }
        verse_end: { type: integer }

    IllustrationResponse:
      type: object
      properties:
        book: { type: string, example: "GEN" }
        name_en: { type: string, example: "Genesis" }
        chapter: { type: integer }
        verse:
          type: integer
          description: Omitted when the whole-chapter form was requested.
        resolution: { type: string, enum: [high, medium] }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              artist: { type: string }
              work_title: { type: string }
              source: { type: string }
              source_url: { type: string }
              caption: { type: string }
              license: { type: string }
              reference: { $ref: "#/components/schemas/IllustrationTarget" }
              url: { type: string }

    GeoThumbnail:
      type: object
      description: >-
        Present only when a place was matched and verified against a
        license-cleared image file. license/credit are per-image and vary
        by source image, distinct from the place/verse data itself.
      properties:
        url: { type: string, example: "https://media.iqbible.com/media/_images/geo/m39ac0b.ia3a355.jpg" }
        license: { type: string, example: "CC-BY-SA-4.0" }
        credit: { type: string, example: "Arch rim" }

    GeoPlace:
      type: object
      description: >-
        lat/lon/confidence_score/modern_name/wikidata_id are all absent
        together when special is set instead — a special marker rather than
        a fabricated coordinate for places that can't be resolved to a
        modern location.
      properties:
        id: { type: integer, example: 1 }
        source_id: { type: string, example: "aea17b7" }
        name: { type: string, example: "Abana" }
        slug: { type: string, example: "abana" }
        preceding_article: { type: string, example: "the" }
        place_type: { type: string, example: "river" }
        class: { type: string, enum: [human, natural, special, "human,natural"] }
        lat: { type: number, format: double, example: 33.513542 }
        lon: { type: number, format: double, example: 36.305 }
        confidence_score: { type: integer, example: 500 }
        special:
          type: string
          enum: [not_a_place, unknown_place, multiple_locations, nonspecific_place, not_a_proper_name, recursive]
        modern_name: { type: string, example: "Barada River" }
        wikidata_id: { type: string, example: "Q765106" }
        thumbnail: { $ref: "#/components/schemas/GeoThumbnail" }

    GeoPlaceVerse:
      type: object
      properties:
        book: { type: string, example: "2KI" }
        chapter: { type: integer, example: 5 }
        verse: { type: integer, example: 12 }
        instance_type: { type: string, example: "name" }

    GeoPlaceDetail:
      allOf:
        - $ref: "#/components/schemas/GeoPlace"
        - type: object
          properties:
            verses:
              type: array
              items: { $ref: "#/components/schemas/GeoPlaceVerse" }

    GeoReferenceResponse:
      type: object
      properties:
        book: { type: string, example: "2KI" }
        name_en: { type: string, example: "2 Kings" }
        chapter: { type: integer }
        verse:
          type: integer
          description: Omitted when the whole-chapter form was requested.
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/GeoPlace" }

    GeoPlacesListResponse:
      type: object
      properties:
        count: { type: integer }
        data:
          type: array
          items: { $ref: "#/components/schemas/GeoPlace" }

  responses:
    BadRequest:
      description: Missing or invalid parameters.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    Unauthorized:
      description: Missing or invalid API key (`api_key_required`).
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    NotFound:
      description: Unknown version, book, chapter or lookup key.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    TooManyRequests:
      description: |
        Per-minute rate (`rate_limited`), monthly quota (`quota_exceeded`), or an
        overage ceiling (`overage_limit_reached`). Honour `Retry-After`.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema: { type: integer }
        X-RateLimit-Remaining:
          description: Requests left in the current minute.
          schema: { type: integer }
        X-RateLimit-Remaining-Month:
          description: Requests left this month.
          schema: { type: integer }
        X-RateLimit-Overage:
          description: Requests served past the monthly quota (billable). Sent only while in overage.
          schema: { type: integer }
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
