openapi: 3.1.0
info:
  title: ScriptureFlow Public API
  version: 0.2.0-preview
  summary: Multilingual Scripture discovery and lookup API
  description: |
    The public preview contract for ScriptureFlow's runtime lookup endpoints and
    generated static JSON resources. Discover exact, case-sensitive version keys
    through `/translations.json`; version keys are machine identifiers such as
    `en-kjv`, not display labels such as `kjv` or `en-KJV`.

    The preview currently requires no authentication. Scripture translation
    licensing and attribution requirements remain applicable to API consumers.
  contact:
    name: ScriptureFlow
    url: https://github.com/Exnav29/ScriptureFlow
  license:
    name: See repository license and translation-specific rights metadata
    url: https://github.com/Exnav29/ScriptureFlow/blob/main/License
servers:
  - url: https://scriptureflow-api-preview.pages.dev
    description: Public preview
security: []
tags:
  - name: Discovery
    description: Public build, translation, and canonical book discovery resources.
  - name: Static Scripture data
    description: Generated translation-scoped JSON resources.
  - name: Runtime lookup
    description: Function-powered verse and passage lookup endpoints.
paths:
  /status.json:
    get:
      tags: [Discovery]
      operationId: getBuildStatus
      summary: Get public build status
      responses:
        '200':
          description: Current generated API build status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /translations.json:
    get:
      tags: [Discovery]
      operationId: listTranslations
      summary: List published translation version keys
      description: Use the exact `version` value returned here in all translation-scoped requests.
      responses:
        '200':
          description: Published translations available to API clients.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranslationList'
              examples:
                translationList:
                  value:
                    - version: en-kjv
                      language_code: eng
                      translation_name: King James Version
                      status: ready
                      books_found: 80
                      chapters_found: 1362
                      verses_found: 36820
                      verses_index_type: split-index
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /public-catalog.json:
    get:
      tags: [Discovery]
      operationId: getPublicCatalog
      summary: Get the public translation catalog
      responses:
        '200':
          description: Catalog summary and published translations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicCatalog'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /canonical-books.json:
    get:
      tags: [Discovery]
      operationId: listCanonicalBooks
      summary: List canonical books and accepted aliases
      responses:
        '200':
          description: Canonical book map used to normalize lookup aliases.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CanonicalBook'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /{version}/translation.json:
    get:
      tags: [Static Scripture data]
      operationId: getTranslation
      summary: Get translation metadata
      parameters:
        - $ref: '#/components/parameters/VersionPath'
      responses:
        '200':
          description: Translation-level metadata and availability totals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Translation'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /{version}/books.json:
    get:
      tags: [Static Scripture data]
      operationId: listTranslationBooks
      summary: List books available in a translation
      parameters:
        - $ref: '#/components/parameters/VersionPath'
      responses:
        '200':
          description: Translation-scoped book index.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Book'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /{version}/chapters.json:
    get:
      tags: [Static Scripture data]
      operationId: listTranslationChapters
      summary: List chapters available in a translation
      parameters:
        - $ref: '#/components/parameters/VersionPath'
      responses:
        '200':
          description: Translation-scoped chapter index.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Chapter'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /{version}/verses-index.json:
    get:
      tags: [Static Scripture data]
      operationId: getVerseIndex
      summary: Get the translation verse index
      description: |
        Small indexes are returned as a JSON array. Large indexes are returned as
        a split-index manifest whose `parts` point to generated part files. Most
        clients should use the runtime lookup endpoints instead of loading an
        entire index.
      parameters:
        - $ref: '#/components/parameters/VersionPath'
      responses:
        '200':
          description: A single verse array or split-index manifest.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerseIndex'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /{version}/random.json:
    get:
      tags: [Static Scripture data]
      operationId: getVerseOfTheDay
      summary: Get the generated Verse of the Day
      description: |
        Returns a stable generated verse for the translation. This is not a live
        random endpoint. Use `/api/quick-verse` when refreshing should select a
        new runtime-random verse.
      parameters:
        - $ref: '#/components/parameters/VersionPath'
      responses:
        '200':
          description: Stable generated Verse of the Day.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RandomVerse'
        '404':
          $ref: '#/components/responses/StaticAssetNotFound'
  /api/verse:
    get:
      tags: [Runtime lookup]
      operationId: lookupVerseOrPassage
      summary: Look up a verse or same-chapter passage
      description: |
        Supply `version` plus either a free-text `reference`, or structured
        `book`, `chapter`, and `verse`/`start_verse` fields. A non-empty
        `reference` takes priority over structured lookup fields. `start_verse`
        is an alias for `verse` and takes priority over it when both are present.
        `end_verse` creates an inclusive, same-chapter range. Cross-chapter ranges
        are not supported.
      parameters:
        - $ref: '#/components/parameters/VersionQuery'
        - name: reference
          in: query
          description: Free-text reference such as `John 3:16` or `Amos 8:4-6`; takes priority when present.
          schema:
            type: string
            maxLength: 120
          example: John 3:16
        - name: book
          in: query
          description: Book name, slug, or supported alias for structured lookup.
          schema:
            type: string
            maxLength: 80
          example: John
        - name: chapter
          in: query
          description: Chapter number for structured lookup.
          schema:
            type: integer
            minimum: 1
            maximum: 200
          example: 3
        - name: verse
          in: query
          description: Starting verse for structured lookup. Used unless `start_verse` is present.
          schema:
            type: integer
            minimum: 1
            maximum: 250
          example: 16
        - name: start_verse
          in: query
          description: Alias for `verse`; takes priority over `verse` when both are supplied.
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - name: end_verse
          in: query
          description: Inclusive end of a same-chapter range, at most 50 verses including the start.
          schema:
            type: integer
            minimum: 1
            maximum: 250
      responses:
        '200':
          description: A single verse or inclusive same-chapter passage.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VerseLookupResponse'
                  - $ref: '#/components/schemas/PassageLookupResponse'
              examples:
                structuredVerse:
                  summary: GET /api/verse?version=en-kjv&book=John&chapter=3&verse=16
                  value:
                    ok: true
                    type: verse_lookup
                    version: en-kjv
                    reference: John 3:16
                    book: john
                    chapter: 3
                    verse: 16
                    text: For God so loved the world...
                    served_at_utc: '2026-06-20T07:14:53.528Z'
                    query: {version: en-kjv, book: John, canonical_book: john, chapter: 3, verse: 16}
                    result: {id: 'en-kjv:john:3:16', version: en-kjv, book: john, book_slug: john, canonical_book: john, chapter: 3, verse: 16, reference: 'John 3:16', text: 'For God so loved the world...', source_path: 'bibles/en-kjv/books/john/chapters/3/verses/16.json', api_path: '/en-kjv/books/john/chapters/3/verses/16.json'}
                    source: {method: book-asset, index: '/en-kjv/books/john.json', api_path: '/en-kjv/books/john/chapters/3/verses/16.json', source_path: 'bibles/en-kjv/books/john/chapters/3/verses/16.json', source_text_origin: book_asset}
                    behavior: {selection: requested_reference, refresh_returns_new_verse: false, runtime_lookup: true, static_generated: false}
                    lookup: {method: book-asset}
                passage:
                  summary: GET /api/verse?version=en-kjv&book=Amos&chapter=8&verse=4&end_verse=6
                  value:
                    ok: true
                    type: passage_lookup
                    version: en-kjv
                    reference: Amos 8:4-6
                    book: amos
                    chapter: 8
                    start_verse: 4
                    end_verse: 6
                    text: "Hear this...\nSaying...\nThat we may buy..."
                    served_at_utc: '2026-06-20T07:14:53.807Z'
                    query: {version: en-kjv, book: Amos, canonical_book: amos, chapter: 8, verse: 4, end_verse: 6}
                    result:
                      - {id: 'en-kjv:amos:8:4', version: en-kjv, book: amos, chapter: 8, verse: 4, reference: 'Amos 8:4', text: 'Hear this...'}
                    verses:
                      - {id: 'en-kjv:amos:8:4', version: en-kjv, book: amos, chapter: 8, verse: 4, reference: 'Amos 8:4', text: 'Hear this...'}
                    source: {method: book-asset, index: '/en-kjv/books/amos.json', api_path: '/en-kjv/books/amos/chapters/8/verses/4.json', source_path: 'bibles/en-kjv/books/amos/chapters/8/verses/4.json', source_text_origin: book_asset}
                    behavior: {selection: requested_passage, refresh_returns_new_verse: false, runtime_lookup: true, static_generated: false}
                    lookup: {method: book-asset}
                freeText:
                  summary: GET /api/verse?version=en-lsv&reference=John%203%3A16
                  value:
                    ok: true
                    type: verse_lookup
                    version: en-lsv
                    reference: John 3:16
                    book: john
                    chapter: 3
                    verse: 16
                    text: for God so loved the world...
                    served_at_utc: '2026-06-20T07:15:00.000Z'
                    query: {version: en-lsv, reference: 'John 3:16', book: John, canonical_book: john, chapter: 3, verse: 16}
                    result: {id: 'en-lsv:john:3:16', version: en-lsv, book: john, chapter: 3, verse: 16, reference: 'John 3:16', text: 'for God so loved the world...'}
                    source: {method: book-asset, index: '/en-lsv/books/john.json', api_path: '/en-lsv/books/john/chapters/3/verses/16.json', source_path: 'bibles/en-lsv/books/john/chapters/3/verses/16.json', source_text_origin: book_asset}
                    behavior: {selection: requested_reference, refresh_returns_new_verse: false, runtime_lookup: true, static_generated: false}
                    lookup: {method: book-asset}
        '400':
          description: Invalid query, reference, version, or numeric range.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidVersion:
                  value: {ok: false, type: verse_lookup, error_code: INVALID_VERSION, error: 'Invalid version. Use a published ScriptureFlow version key such as en-lsv.', query: {version: ../secret}}
                bookTooLong:
                  value: {ok: false, type: verse_lookup, error_code: BOOK_TOO_LONG, error: 'Book value is too long. Maximum length is 80 characters.', query: {version: en-lsv, book: '...'}}
                referenceTooLong:
                  value: {ok: false, type: verse_lookup, error_code: REFERENCE_TOO_LONG, error: 'Reference is too long. Maximum length is 120 characters.', query: {version: en-lsv, reference: '...'}}
                invalidReference:
                  value: {ok: false, type: verse_lookup, error_code: INVALID_REFERENCE, error: 'Invalid reference: cross-chapter ranges are not supported yet', query: {version: en-lsv, reference: 'John 3:16-4:2'}}
                invalidQuery:
                  value: {ok: false, type: verse_lookup, error_code: INVALID_QUERY, error: 'Invalid query. Required: version plus either reference (e.g. John 3:16) or book/chapter/verse.', query: {version: en-lsv}}
                chapterOutOfRange:
                  value: {ok: false, type: verse_lookup, error_code: CHAPTER_OUT_OF_RANGE, error: 'Chapter must be between 1 and 200.', query: {version: en-lsv, book: John, chapter: 201, verse: 1}}
                verseOutOfRange:
                  value: {ok: false, type: verse_lookup, error_code: VERSE_OUT_OF_RANGE, error: 'Verse must be between 1 and 250.', query: {version: en-lsv, book: John, chapter: 3, verse: 251}}
                endVerseOutOfRange:
                  value: {ok: false, type: passage_lookup, error_code: END_VERSE_OUT_OF_RANGE, error: 'End verse must be between 1 and 250.', query: {version: en-lsv, book: John, chapter: 3, verse: 1, end_verse: 251}}
                invalidVerseRange:
                  value: {ok: false, type: passage_lookup, error_code: INVALID_VERSE_RANGE, error: 'End verse must be greater than or equal to the starting verse.', query: {version: en-lsv, book: John, chapter: 3, verse: 16, end_verse: 15}}
                rangeTooLarge:
                  value: {ok: false, type: passage_lookup, error_code: PASSAGE_RANGE_TOO_LARGE, error: 'Passage range is too large. Maximum range is 50 verses.', query: {version: en-lsv, book: John, chapter: 3, verse: 1, end_verse: 60}}
        '404':
          description: Translation book asset or one or more requested verses were not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                bookAssetNotFound:
                  value: {ok: false, type: verse_lookup, error_code: BOOK_ASSET_NOT_FOUND, error: 'Book lookup asset not found', query: {version: en-lsv, book: Unknown, chapter: 1, verse: 1}}
                requestedVersesNotFound:
                  value: {ok: false, type: passage_lookup, error_code: REQUESTED_VERSES_NOT_FOUND, error: 'One or more requested verses were not found', query: {version: en-lsv, book: John, chapter: 3, verse: 249, end_verse: 250}, missing_verses: [249, 250]}
        '500':
          description: Required canonical book metadata was unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                canonicalMapUnavailable:
                  value: {ok: false, type: verse_lookup, error_code: CANONICAL_BOOK_MAP_UNAVAILABLE, error: 'Canonical book map not available'}
  /api/quick-verse:
    get:
      tags: [Runtime lookup]
      operationId: getQuickVerse
      summary: Get a runtime-selected random verse
      description: |
        Selects a verse at request time from the requested translation. Refreshing
        may return a different verse. This is distinct from the stable generated
        `/{version}/random.json` Verse of the Day. Internally, the endpoint can use
        either a single verse index or a split-index manifest. Responses currently
        use `Cache-Control: no-store`.
      parameters:
        - $ref: '#/components/parameters/VersionQuery'
      responses:
        '200':
          description: Runtime-selected verse. The response is not stored by caches.
          headers:
            Cache-Control:
              description: Current runtime cache directive.
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuickVerseResponse'
              examples:
                kjv:
                  summary: GET /api/quick-verse?version=en-kjv
                  value:
                    ok: true
                    type: quick_verse
                    version: en-kjv
                    reference: John 3:16
                    book: john
                    chapter: 3
                    verse: 16
                    text: For God so loved the world...
                    served_at_utc: '2026-06-20T07:14:54.112Z'
                    result: {id: 'en-kjv:john:3:16', version: en-kjv, book: john, chapter: 3, verse: 16, reference: 'John 3:16', text: 'For God so loved the world...'}
                    behavior: {selection: runtime_random, refresh_returns_new_verse: true, translation_scoped: true, static_generated: false}
                    source: {method: runtime_random, index: '/en-kjv/verses-index.json', api_path: '/en-kjv/books/john/chapters/3/verses/16.json', source_path: 'bibles/en-kjv/books/john/chapters/3/verses/16.json', source_text_origin: verse_index}
                    lookup: {method: split-index, index: '/en-kjv/verses-index.json', split_part: 1, split_part_path: '/en-kjv/verses-index-parts/part-001.json'}
                lsv:
                  summary: GET /api/quick-verse?version=en-lsv
                  value:
                    ok: true
                    type: quick_verse
                    version: en-lsv
                    reference: John 3:16
                    book: john
                    chapter: 3
                    verse: 16
                    text: for God so loved the world...
                    served_at_utc: '2026-06-20T07:15:00.000Z'
                    result: {id: 'en-lsv:john:3:16', version: en-lsv, book: john, chapter: 3, verse: 16, reference: 'John 3:16', text: 'for God so loved the world...'}
                    behavior: {selection: runtime_random, refresh_returns_new_verse: true, translation_scoped: true, static_generated: false}
                    source: {method: runtime_random, index: '/en-lsv/verses-index.json', api_path: '/en-lsv/books/john/chapters/3/verses/16.json', source_path: 'bibles/en-lsv/books/john/chapters/3/verses/16.json', source_text_origin: verse_index}
                    lookup: {method: single-index, index: '/en-lsv/verses-index.json'}
        '400':
          description: Invalid or missing version key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidVersion:
                  value: {ok: false, type: quick_verse, error_code: INVALID_VERSION, error: 'Invalid version. Use a published ScriptureFlow version key such as en-kjv.', version: '../secret', query: {version: '../secret'}}
        '404':
          description: Version index or usable verse entries were not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                versionIndexNotFound:
                  value: {ok: false, type: quick_verse, error_code: VERSION_INDEX_NOT_FOUND, error: 'Version index not found', version: en-missing, query: {version: en-missing}, index: '/en-missing/verses-index.json'}
                noUsableEntries:
                  value: {ok: false, type: quick_verse, error_code: NO_USABLE_VERSE_ENTRIES, error: 'No usable verse entries found for this translation', version: en-example}
                noUsableParts:
                  value: {ok: false, type: quick_verse, error_code: NO_USABLE_SPLIT_INDEX_PARTS, error: 'Split index does not contain usable part files', version: en-example}
        '500':
          description: Verse text, split-index part, or index format could not be resolved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                verseTextNotResolved:
                  value: {ok: false, type: quick_verse, error_code: VERSE_TEXT_NOT_RESOLVED, error: 'Random verse was selected, but verse text could not be resolved', version: en-example, selected: {book: john, chapter: 3, verse: 16, api_path: '/en-example/books/john/chapters/3/verses/16.json'}}
                splitPartNotLoaded:
                  value: {ok: false, type: quick_verse, error_code: SPLIT_INDEX_PART_NOT_LOADED, error: 'Selected split-index part could not be loaded', version: en-example, split_part_path: '/en-example/verses-index-parts/part-001.json'}
                unsupportedIndex:
                  value: {ok: false, type: quick_verse, error_code: UNSUPPORTED_INDEX_FORMAT, error: 'Unsupported verses-index.json format', version: en-example, query: {version: en-example}, index: '/en-example/verses-index.json'}
components:
  parameters:
    VersionPath:
      name: version
      in: path
      required: true
      description: Exact, case-sensitive machine identifier from `/translations.json` (for example `en-kjv`, not `kjv` or `en-KJV`).
      schema:
        type: string
        minLength: 3
        maxLength: 80
      example: en-kjv
    VersionQuery:
      name: version
      in: query
      required: true
      description: Exact machine identifier from `/translations.json` (for example `en-kjv`, not `kjv` or `en-KJV`).
      schema:
        type: string
        minLength: 3
        maxLength: 80
        pattern: '^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+$'
      example: en-kjv
  responses:
    StaticAssetNotFound:
      description: The generated resource or version was not found. Static-host responses may not use the runtime `ErrorResponse` envelope.
  schemas:
    Status:
      type: object
      additionalProperties: false
      required: [generated_at, total_discovered, total_ready, total_warning, total_failed, split_indexes, split_index_translations, failed]
      properties:
        generated_at: {type: string, format: date-time}
        total_discovered: {type: integer, minimum: 0}
        total_ready: {type: integer, minimum: 0}
        total_warning: {type: integer, minimum: 0}
        total_failed: {type: integer, minimum: 0}
        split_indexes: {type: integer, minimum: 0}
        split_index_translations:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [version, part_count, largest_part_bytes]
            properties:
              version: {type: string}
              part_count: {type: integer, minimum: 1}
              largest_part_bytes: {type: integer, minimum: 0}
        failed:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [version, failures]
            properties:
              version: {type: string}
              failures: {type: array, items: {type: string}}
    Translation:
      type: object
      additionalProperties: false
      required: [version, language_code, translation_name]
      properties:
        version: {type: string, minLength: 1, description: Exact machine identifier used in API paths and queries.}
        language_code: {type: string, minLength: 1}
        language_name: {type: string}
        translation_name: {type: string, minLength: 1}
        translation_abbreviation: {type: string}
        direction: {type: [string, 'null'], enum: [ltr, rtl, ttb, btt, null]}
        books_available: {type: array, items: {type: string, minLength: 1}}
        total_books: {type: integer, minimum: 0}
        total_chapters: {type: integer, minimum: 0}
        total_verses: {type: integer, minimum: 0}
        status: {type: string, enum: [ready, warning]}
        books_found: {type: integer, minimum: 0}
        chapters_found: {type: integer, minimum: 0}
        verses_found: {type: integer, minimum: 0}
        verses_index_type: {type: string, enum: [single-file, split-index]}
        source:
          type: object
          additionalProperties: false
          required: [source_path]
          properties:
            source_path: {type: string}
            metadata_path: {type: string}
            source_type: {type: string}
            source_version: {type: string}
        license: {type: string}
        copyright: {type: string}
        metadata: {type: object, additionalProperties: true}
    TranslationList:
      type: array
      items:
        $ref: '#/components/schemas/Translation'
    PublicCatalog:
      type: object
      additionalProperties: false
      required: [generated_at, total_discovered, total_ready, total_warning, total_failed, translations]
      properties:
        generated_at: {type: string, format: date-time}
        total_discovered: {type: integer, minimum: 0}
        total_ready: {type: integer, minimum: 0}
        total_warning: {type: integer, minimum: 0}
        total_failed: {type: integer, minimum: 0}
        translations:
          $ref: '#/components/schemas/TranslationList'
    CanonicalBook:
      type: object
      additionalProperties: false
      required: [canonical_book, osis_id, usfm_id, order, english_name, aliases, localized_aliases]
      properties:
        canonical_book: {type: string, minLength: 1}
        osis_id: {type: string, minLength: 1}
        usfm_id: {type: string, minLength: 1}
        order: {type: integer, minimum: 1}
        english_name: {type: string, minLength: 1}
        aliases: {type: array, items: {type: string}}
        localized_aliases:
          type: object
          additionalProperties:
            type: array
            items: {type: string}
    Book:
      type: object
      additionalProperties: false
      required: [id, version, book, book_slug, chapter_count, verse_count, source_path, api_path]
      properties:
        id: {type: string, minLength: 1}
        version: {type: string, minLength: 1}
        language_code: {type: string}
        book: {type: string, minLength: 1}
        book_slug: {type: string, minLength: 1}
        canonical_book: {type: [string, 'null']}
        book_number: {type: [integer, 'null'], minimum: 1}
        testament: {type: [string, 'null'], enum: [old, new, deuterocanon, other, null]}
        chapter_count: {type: integer, minimum: 0}
        verse_count: {type: integer, minimum: 0}
        source_path: {type: string, minLength: 1}
        api_path: {type: string, minLength: 1}
        metadata: {type: object, additionalProperties: true}
    Chapter:
      type: object
      additionalProperties: false
      required: [id, version, book, book_slug, chapter, reference, source_path, api_path, verse_count]
      properties:
        id: {type: string, minLength: 1}
        version: {type: string, minLength: 1}
        language_code: {type: string}
        book: {type: string, minLength: 1}
        book_slug: {type: string, minLength: 1}
        canonical_book: {type: [string, 'null']}
        chapter: {type: integer, minimum: 1}
        reference: {type: string, minLength: 1}
        source_path: {type: string, minLength: 1}
        api_path: {type: string, minLength: 1}
        verse_count: {type: integer, minimum: 0}
        verses:
          type: array
          items:
            type: object
            additionalProperties: true
            required: [verse, text]
            properties:
              verse: {type: [integer, string]}
              text: {type: string}
        metadata: {type: object, additionalProperties: true}
    Verse:
      type: object
      additionalProperties: true
      required: [version, book, chapter, verse, reference, text]
      properties:
        id: {type: string}
        version: {type: string, minLength: 1}
        language_code: {type: string}
        language_name: {type: string}
        translation_name: {type: string}
        translation_abbreviation: {type: string}
        book: {type: string, minLength: 1}
        book_slug: {type: string}
        canonical_book: {type: [string, 'null']}
        book_number: {type: [integer, 'null'], minimum: 1}
        chapter: {type: integer, minimum: 1}
        verse: {type: integer, minimum: 1}
        reference: {type: string, minLength: 1}
        text: {type: string}
        testament: {type: [string, 'null'], enum: [old, new, deuterocanon, other, null]}
        source_path: {type: string}
        api_path: {type: string}
        canonical_order: {type: [integer, 'null'], minimum: 1}
        metadata: {type: object, additionalProperties: true}
    VerseIndex:
      description: Single-file verse array or split-index manifest.
      oneOf:
        - type: array
          items:
            $ref: '#/components/schemas/Verse'
        - $ref: '#/components/schemas/SplitVerseIndex'
    SplitVerseIndex:
      type: object
      additionalProperties: false
      required: [version, type, total_verses, part_count, max_part_size_bytes, parts]
      properties:
        version: {type: string}
        type: {type: string, const: split-index}
        total_verses: {type: integer, minimum: 0}
        part_count: {type: integer, minimum: 1}
        max_part_size_bytes: {type: integer, minimum: 1}
        parts:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [part, verse_count, path, size_bytes]
            properties:
              part: {type: integer, minimum: 1}
              verse_count: {type: integer, minimum: 0}
              path: {type: string, minLength: 1}
              size_bytes: {type: integer, minimum: 0}
    RandomVerse:
      type: object
      additionalProperties: false
      required: [version, reference, book, chapter, verse, text, source_path, api_path]
      properties:
        version: {type: string}
        reference: {type: string}
        book: {type: string}
        chapter: {type: integer, minimum: 1}
        verse: {type: integer, minimum: 1}
        text: {type: string}
        source_path: {type: string}
        api_path: {type: string}
    SourceMetadata:
      type: object
      additionalProperties: false
      required: [method, index, api_path, source_path, source_text_origin]
      properties:
        method: {type: string, enum: [book-asset, runtime_random]}
        index: {type: string}
        api_path: {type: [string, 'null']}
        source_path: {type: [string, 'null']}
        source_text_origin: {type: string, enum: [book_asset, verse_index, verse_asset_file]}
    BehaviorMetadata:
      type: object
      additionalProperties: false
      required: [selection, refresh_returns_new_verse, static_generated]
      properties:
        selection: {type: string, enum: [requested_reference, requested_passage, runtime_random]}
        refresh_returns_new_verse: {type: boolean}
        runtime_lookup: {type: boolean}
        translation_scoped: {type: boolean}
        static_generated: {type: boolean}
    LookupMetadata:
      type: object
      additionalProperties: false
      required: [method]
      properties:
        method: {type: string, enum: [book-asset, single-index, split-index]}
        index: {type: string}
        split_part: {type: [integer, 'null'], minimum: 1}
        split_part_path: {type: string}
    LookupQuery:
      type: object
      additionalProperties: true
      required: [version]
      properties:
        version: {type: string}
        reference: {type: string}
        book: {type: string}
        canonical_book: {type: [string, 'null']}
        chapter: {type: integer}
        verse: {type: integer}
        end_verse: {type: integer}
    VerseLookupResponse:
      type: object
      additionalProperties: false
      required: [ok, type, version, reference, book, chapter, verse, text, served_at_utc, query, result, source, behavior, lookup]
      properties:
        ok: {type: boolean, const: true}
        type: {type: string, const: verse_lookup}
        version: {type: string}
        reference: {type: string}
        book: {type: string}
        chapter: {type: integer, minimum: 1}
        verse: {type: integer, minimum: 1}
        text: {type: string}
        served_at_utc: {type: string, format: date-time}
        query: {$ref: '#/components/schemas/LookupQuery'}
        result: {$ref: '#/components/schemas/Verse'}
        source: {$ref: '#/components/schemas/SourceMetadata'}
        behavior: {$ref: '#/components/schemas/BehaviorMetadata'}
        lookup: {$ref: '#/components/schemas/LookupMetadata'}
    PassageLookupResponse:
      type: object
      additionalProperties: false
      required: [ok, type, version, reference, book, chapter, start_verse, end_verse, text, served_at_utc, query, result, verses, source, behavior, lookup]
      properties:
        ok: {type: boolean, const: true}
        type: {type: string, const: passage_lookup}
        version: {type: string}
        reference: {type: string}
        book: {type: string}
        chapter: {type: integer, minimum: 1}
        start_verse: {type: integer, minimum: 1, maximum: 250}
        end_verse: {type: integer, minimum: 1, maximum: 250}
        text: {type: string, description: Verse texts joined with newline characters.}
        served_at_utc: {type: string, format: date-time}
        query: {$ref: '#/components/schemas/LookupQuery'}
        result: {type: array, items: {$ref: '#/components/schemas/Verse'}}
        verses: {type: array, items: {$ref: '#/components/schemas/Verse'}}
        source: {$ref: '#/components/schemas/SourceMetadata'}
        behavior: {$ref: '#/components/schemas/BehaviorMetadata'}
        lookup: {$ref: '#/components/schemas/LookupMetadata'}
    QuickVerseResponse:
      type: object
      additionalProperties: true
      required: [ok, type, version, reference, book, chapter, verse, text, served_at_utc, result, behavior, source, lookup]
      properties:
        ok: {type: boolean, const: true}
        type: {type: string, const: quick_verse}
        version: {type: string}
        reference: {type: string}
        book: {type: string}
        chapter: {type: integer, minimum: 1}
        verse: {type: integer, minimum: 1}
        text: {type: string}
        served_at_utc: {type: string, format: date-time}
        result: {$ref: '#/components/schemas/Verse'}
        behavior: {$ref: '#/components/schemas/BehaviorMetadata'}
        source: {$ref: '#/components/schemas/SourceMetadata'}
        lookup: {$ref: '#/components/schemas/LookupMetadata'}
        book_slug: {type: string}
        canonical_book: {type: [string, 'null']}
        language_code: {type: string}
        language_name: {type: string}
        translation_name: {type: string}
        translation_abbreviation: {type: string}
    ErrorResponse:
      type: object
      additionalProperties: true
      required: [ok, type, error_code, error]
      properties:
        ok: {type: boolean, const: false}
        type: {type: string, enum: [verse_lookup, passage_lookup, quick_verse]}
        error_code:
          type: string
          enum: [INVALID_VERSION, BOOK_TOO_LONG, REFERENCE_TOO_LONG, INVALID_REFERENCE, INVALID_QUERY, CHAPTER_OUT_OF_RANGE, VERSE_OUT_OF_RANGE, END_VERSE_OUT_OF_RANGE, INVALID_VERSE_RANGE, PASSAGE_RANGE_TOO_LARGE, BOOK_ASSET_NOT_FOUND, REQUESTED_VERSES_NOT_FOUND, CANONICAL_BOOK_MAP_UNAVAILABLE, VERSION_INDEX_NOT_FOUND, NO_USABLE_VERSE_ENTRIES, VERSE_TEXT_NOT_RESOLVED, NO_USABLE_SPLIT_INDEX_PARTS, SPLIT_INDEX_PART_NOT_LOADED, UNSUPPORTED_INDEX_FORMAT]
        error: {type: string}
        version: {type: string}
        query: {$ref: '#/components/schemas/LookupQuery'}
        index: {type: string}
        missing_verses: {type: array, items: {type: integer}}
        split_part_path: {type: string}
        selected:
          type: object
          additionalProperties: false
          properties:
            book: {type: [string, 'null']}
            chapter: {type: integer}
            verse: {type: integer}
            api_path: {type: [string, 'null']}
