openapi: 3.0.0 info: title: 'Quotes API — Islamic Network' description: 'The quotes.islamic.network corpus: each quote is an utterance in one original language plus translations of it, with its author from the shared people registry and its verbatim citation. Every response is wrapped in the `{code, status, data}` envelope shared across Islamic Network APIs; list endpoints add a `meta` sibling. Language keys are BCP 47 tags; empty values are omitted entirely.' contact: url: 'https://islamic.network' email: hello@islamic.network version: 1.0.0 servers: - url: 'https://quotes.api.islamic.network/v1' description: Production paths: '/documentation/openapi/{format}': get: tags: - documentation summary: 'This specification, as OpenAPI 3' operationId: getOpenApiSpec parameters: - name: format in: path required: true schema: type: string enum: - yaml - json responses: '200': description: 'The OpenAPI document.' '/people/{slug}/quotes': get: tags: - people summary: "A person's quotes" description: 'The endpoint the People API aggregates. Items are summaries (id, original, source); the full renditions live at /quotes/{id}. `registry` points at the canonical person record on the People API.' operationId: getPersonQuotes parameters: - $ref: '#/components/parameters/slug' - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/limit' responses: '200': description: 'The person and their quotes.' content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - { properties: { meta: { $ref: '#/components/schemas/Meta' }, data: { properties: { person: { $ref: '#/components/schemas/PersonBlock' }, quotes: { type: array, items: { $ref: '#/components/schemas/QuoteSummary' } } }, type: object } }, type: object } '404': $ref: '#/components/responses/Error' /quotes: get: tags: - quotes summary: 'All quotes (full entries), legacy-id order' description: 'Entries are full — quotes are small, there is nothing to summarize away. Ids are the original quotes.islamic.network numeric ids, preserving URL continuity.' operationId: listQuotes parameters: - $ref: '#/components/parameters/author' - $ref: '#/components/parameters/tag' - $ref: '#/components/parameters/lang' - $ref: '#/components/parameters/original' - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/limit' responses: '200': description: 'Paginated quotes.' content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - { properties: { meta: { $ref: '#/components/schemas/Meta' }, data: { type: array, items: { $ref: '#/components/schemas/Quote' } } }, type: object } /quotes/random: get: tags: - quotes summary: "One random quote — the site's raison d'être" description: 'Same shape as /quotes/{id}; accepts the same filters as the list. Served with no-store cache headers — every request draws afresh.' operationId: getRandomQuote parameters: - $ref: '#/components/parameters/author' - $ref: '#/components/parameters/tag' - $ref: '#/components/parameters/lang' - $ref: '#/components/parameters/original' responses: '200': description: 'A quote from the filtered set.' content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - { properties: { data: { $ref: '#/components/schemas/Quote' } }, type: object } '404': $ref: '#/components/responses/Error' '/quotes/{id}': get: tags: - quotes summary: 'Single quote' operationId: getQuote parameters: - name: id in: path description: "The quote's public numeric id." required: true schema: type: integer minimum: 1 responses: '200': description: 'The quote.' content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - { properties: { data: { $ref: '#/components/schemas/Quote' } }, type: object } '404': $ref: '#/components/responses/Error' /search: get: tags: - search summary: 'Diacritic-folded search across quotes' description: 'Matching is diacritic-folded (Ṣ→s, ā→a, ʿ/ʾ dropped, Arabic tashkīl stripped) across the original text, every translation, sources and tags — `bistami`, `Bisṭāmī` and `الْبِسْطَامِي` all match. Multiple query words AND together.' operationId: search parameters: - name: q in: query description: 'The query; at least 2 characters.' required: true schema: type: string example: remembrance minLength: 2 - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/limit' responses: '200': description: 'Matching quotes as summaries with the field paths that matched.' content: application/json: schema: allOf: - { $ref: '#/components/schemas/Envelope' } - { properties: { meta: { $ref: '#/components/schemas/Meta' }, data: { properties: { query: { type: string }, total: { type: integer }, results: { type: array, items: { $ref: '#/components/schemas/SearchResult' } } }, type: object } }, type: object } '400': $ref: '#/components/responses/Error' components: schemas: Name: description: 'Language-keyed text. Keys are BCP 47 tags; `ar-Latn` is the transliteration. Only non-empty languages appear.' type: object example: ar: 'بَايَزِيد الْبِسْطَامِي' ar-Latn: 'Bāyazīd al-Bisṭāmī' en: 'Bayazid Bistami' additionalProperties: type: string Original: description: 'The quote as uttered or written — identity ("this is the source"), unlike translations which carry provenance ("this is how the copy was made").' required: - language - text properties: language: description: 'BCP 47 tag.' type: string example: en text: type: string type: object Translations: description: 'Renditions keyed by BCP 47 tag; the original never appears here. Adding a language is a translation row in the CMS — it appears additively.' type: object additionalProperties: required: - text properties: text: type: string provenance: description: 'How the rendition was produced; omitted when unknown.' type: string example: claude-retranslation-from-en type: object Author: description: 'The author from the shared people registry; omitted for anonymous and scriptural quotes — the verbatim citation always survives in `source`.' required: - slug - name - url properties: slug: type: string example: bayazid-bistami name: $ref: '#/components/schemas/Name' honorific: $ref: '#/components/schemas/Name' kind: type: string example: wali url: description: 'The canonical person record on the People API.' type: string type: object Meta: description: 'Pagination block, sibling of `data` on list endpoints.' required: - total - page - limit - pages properties: total: type: integer page: type: integer limit: type: integer pages: type: integer type: object Envelope: required: - code - status properties: code: type: integer example: 200 status: type: string example: OK type: object Quote: description: 'A quote in full — lists, items and /quotes/random all serve this shape.' required: - id - url - original properties: id: description: 'The original quotes.islamic.network numeric id, preserving URL continuity.' type: integer example: 1 url: type: string original: $ref: '#/components/schemas/Original' translations: $ref: '#/components/schemas/Translations' author: $ref: '#/components/schemas/Author' tags: description: 'Shared-taxonomy topic slugs; omitted while untagged.' type: array items: type: string source: description: 'The citation exactly as authored.' type: string example: 'Bāyazīd Bisṭāmī ق' type: object QuoteSummary: description: "A person's quote as listed on /people/{slug}/quotes." required: - id - original - url properties: id: type: integer original: $ref: '#/components/schemas/Original' source: type: string url: type: string type: object PersonBlock: required: - slug - name - registry properties: slug: type: string name: $ref: '#/components/schemas/Name' honorific: $ref: '#/components/schemas/Name' kind: type: string registry: description: 'The canonical person record on the People API.' type: string type: object SearchResult: required: - type - matched_in - id - original - url properties: type: type: string enum: - quote matched_in: description: 'Rendition paths that matched, e.g. `original.text`, `translations.fa.text`, `source`, `tags`.' type: array items: type: string id: type: integer original: $ref: '#/components/schemas/Original' source: type: string url: type: string type: object responses: Error: description: 'Error — the envelope with a message string as `data`.' content: application/json: schema: required: - code - status - data properties: code: type: integer example: 404 status: type: string example: 'NOT FOUND' data: type: string example: 'Quote not found.' type: object parameters: page: name: page in: query description: 'Page number (default 1).' schema: type: integer default: 1 minimum: 1 limit: name: limit in: query description: 'Records per page (default 50, max 200).' schema: type: integer default: 50 maximum: 200 minimum: 1 slug: name: slug in: path description: "The person's canonical, ecosystem-wide slug." required: true schema: type: string author: name: author in: query description: 'Only quotes by this person (registry slug).' schema: type: string example: bayazid-bistami tag: name: tag in: query description: 'Only quotes carrying this topic tag.' schema: type: string example: knowledge lang: name: lang in: query description: 'Only quotes having this rendition (as original or translation).' schema: type: string example: ur original: name: original in: query description: 'Only quotes uttered/written in this language.' schema: type: string example: ar tags: - name: quotes description: 'The quotes themselves' - name: people description: 'Quotes by author (shared registry slugs)' - name: search description: 'Diacritic-folded search' - name: documentation description: 'This specification'