Catalogue lookup — which books, headings, authors and shelves exist, not page text. `kind`:
- book (default): titles, authors, publication lines, subject descriptions → book_ids, installed or not. no_title_match: not proof the book is held. downloaded:false → another edition (shamela_get_details(kind='book') same_group) or catalogue metadata only.
- heading: chapter/section headings, i.e. where books treat a topic; open with shamela_read(unit='section', book_id, title_id). No hits = no heading carries the words: drop words or use shamela_search(match='words').
- author: names and biographies → author_id, Hijri death year, book count; no scope. Empty is not absence: retry kunya, nisba or shuhra (ابن قدامة / الموفق / المقدسي).
- category: the 41 flat shelves with catalogue and installed counts → scope.category_ids. A subject spans ids (Qur'anic studies 3 tafsir, 4 Qur'anic sciences/usul al-tafsir, 5 tajwid/qira'at; fiqh 14–17 by madhab): pass all that apply. downloaded_only:true when choosing shelves to search or read.
- work: verify a cited title (optional author, edition); refuses rather than guesses; catalogue only, text need not be installed.
- name: fuzzy best guess over book/author names, for an id; always answers, so never use it to verify a citation.
- period: books by Hijri window, bounds ANDed (scope.period_* on searches unions them).
Rules: a parameter of another kind is refused; book/heading/author/period page with limit/offset until has_more is false. Instead: page text → shamela_search; a printed page → shamela_cite(mode='printed_page'); one book's headings → shamela_read(unit='toc').
View input schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1,
"description": "[kind=book] Arabic words matched against book name + author + bibliography and the editorial subject description; each hit's `matched_in` says which. 1,729 entries carry a description (~1,500 characters on average), which finds a book by SUBJECT: «الرجز» matches 0 titles and 2 descriptions; {query:'البيوع'} also returns books whose description carries it. E.g. {query:'الأصول'}. [kind=heading] Arabic words matched against chapter / section title text, e.g. 'باب الصيام', or 'تعريف' with scope:{book_ids:[id]}. [kind=author] Arabic words matched against name + biography in the ~3,200-author catalogue, e.g. 'ابن قدامة'. [kind=name] An Arabic name fragment: 'ابن قدامة' resolves Ibn Qudamah, 'روضة الناظر' the book, 'الروضة' the books with that word in their title; «title لِauthor» is split into title and author. Every kind reads pre-built indexes: no download needed. (kind=book|heading|author|name only.)"
},
"scope": {
"type": "object",
"properties": {
"book_ids": {
"type": "array",
"items": {
"type": "integer",
"exclusiveMinimum": 0
},
"description": "Restrict to these book IDs. Use `shamela_find(kind='name')` or `shamela_list_downloaded_books` to find IDs. Combine with other scope keys for intersection."
},
"author_ids": {
"type": "array",
"items": {
"type": "integer",
"exclusiveMinimum": 0
},
"description": "Restrict to books by these authors. Includes co-authored books. Use `shamela_find(kind='name', type='author')` to find IDs."
},
"category_ids": {
"type": "array",
"items": {
"type": "integer",
"exclusiveMinimum": 0
},
"description": "Restrict to books in these categories. Categories are flat in master.db (no parent_id, no transitive subtree expansion). Use `shamela_find(kind='category')` to find IDs."
},
"period_from": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive lower bound. Matches book.book_date (composition year) OR author.death_number (death year). Pair with period_to."
},
"period_to": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive upper bound. Pair with period_from."
},
"downloaded_only": {
"type": "boolean",
"description": "Restrict to books actually downloaded on this user's machine (master.db.book.major_ondisk > 0). Critical for honest research scoping when using `shamela_search(match='words')` — only downloaded books have searchable content. Default: false."
},
"madhab": {
"type": "array",
"items": {
"type": "string",
"enum": [
"hanafi",
"maliki",
"shafii",
"hanbali"
]
},
"description": "Restrict to books of these Sunni schools. master.db has NO madhab field, so this is resolved two ways: a book filed under a school-specific category (الفقه الحنفي/المالكي/الشافعي/الحنبلي) counts directly, and otherwise the MAIN AUTHOR's school is used, inferred from the categories that author's own books are filed under. That second path is what reaches مسائل فقهية and الفقه العام, where Shamela records no school — it raises madhab coverage of the fiqh shelves from 27% to 43%. Books whose school cannot be established are EXCLUDED by this filter, so do not read absence as 'no school said this'; for comparative questions leave madhab unset and scope by category instead."
},
"book_type": {
"type": "array",
"items": {
"type": "string",
"enum": [
"book",
"journal",
"manuscript",
"thesis",
"electronic",
"transcript"
]
},
"description": "Restrict to these kinds of entry (master.db.book.book_type — the same filter row Shamela's own 'اختيار كتاب' window shows). 'transcript' (تفريغات) is a transcribed lecture series, not an authored book, and 'thesis'/'electronic' are likewise not printed works — excluding them sharpens a scholarly search. Of 8,594 entries, 7,334 are 'book'; 1,260 are not."
},
"citable_only": {
"type": "boolean",
"description": "Keep only books whose page numbering matches a real printed edition (master.db.book.printed == 1 — Shamela's «يوافق المطبوع»). The other 1,417 entries are paginated by Shamela automatically, so their page numbers are NOT a citable reference to any published edition. Set this true whenever the answer will quote a page number as a citation. Default: false."
}
},
"additionalProperties": false,
"description": "[kind=book] Restrict to authors, categories, a Hijri period (period_from/period_to) or downloaded_only (books whose text is installed); scope.book_ids is not accepted. E.g. {query:'فقه', scope:{category_ids:[17], downloaded_only:true}}. [kind=heading] The same filter as shamela_search(match='words'); scope.book_ids with one id searches only that book's headings. EMPTY_SCOPE: the scope matched no book. (kind=book|heading only.)"
},
"options": {
"type": "object",
"properties": {
"morphology": {
"type": "boolean",
"description": "Search Arabic root forms via the AlKhalil morphological analyzer. Useful when looking for all word forms (يكتب، كاتب، مكتوب…) of a root (كتب). Cannot be combined with `wildcards`. Default: false."
},
"wildcards": {
"type": "boolean",
"description": "Interpret `*` (zero-or-more characters) and `?` (exactly-one character) within tokens. Examples: `صيا*` matches صيام/صيامهم, `ص?م` matches صام/صوم/صمم. Leading wildcards (`*ابن`) are slow. Cannot combine with `morphology`. With this OFF, `*` and `?` are treated as ordinary punctuation and split the word, so a stray one no longer zeroes the search. CAVEAT on prefixes: the index folds some words as a WHOLE — «مائة» is stored as «ميه», «داوود» as «داود» — and a wildcard stem is folded without the letters the wildcard replaced, so `مائ*` becomes `ماي*` and reaches مائع/مائدة but NOT مائة. When a prefix search returns less than you expect for a word like these, search the full word without a wildcard instead. Default: false."
},
"sort": {
"type": "string",
"enum": [
"relevance",
"chronological"
],
"description": "Result order. 'relevance' (default) ranks by how well the page matches — the right answer to 'find me X'. 'chronological' orders by the author's death year, then book, then page, so the EARLIEST sources come first — the right answer to 'who says this first' or 'how does this idea travel through the tradition'. Undated books sort last."
},
"preserve_diacritics": {
"type": "boolean",
"description": "Returns OPTION_NOT_SUPPORTED here. Default: false."
},
"preserve_hamza": {
"type": "boolean",
"description": "Returns OPTION_NOT_SUPPORTED here. Default: false."
},
"preserve_digits": {
"type": "boolean",
"description": "Returns OPTION_NOT_SUPPORTED here. Default: false."
},
"search_in": {
"type": "array",
"items": {
"type": "string",
"enum": [
"body",
"foot"
]
},
"description": "Ignored here. Default: ['body','foot']."
},
"relax_when_empty": {
"type": "boolean",
"description": "When the AND of every word of the title matches no catalogue entry, retry requiring only the largest number of them some entry does carry, and report it in `relaxed_min_match`. A title is usually quoted the way people cite it («فتح الباري لابن حجر») rather than the way the catalogue spells it («فتح الباري بشرح البخاري - ط السلفية»), and the strict AND then reports the library does not hold the book at all. Default: true."
}
},
"additionalProperties": false,
"description": "[kind=book] morphology / wildcards / sort / relax_when_empty. [kind=heading] morphology / wildcards / sort ('chronological' = earliest author first); headings are matched in folded form only. [kind=author] morphology / wildcards / sort ('relevance' by default; 'chronological' = by Hijri death year, earliest first, undated last), e.g. {query:'الشافعي', options:{wildcards:false}}. preserve_* return OPTION_NOT_SUPPORTED; search_in is ignored (catalogue entries have no body or footnotes; a heading is a single field). Results are ranked by relevance unless sort says otherwise. (kind=book|heading|author only.)"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "[kind=book|heading|author|period] Maximum number of results to return (1-100, default 20). [kind=work] Maximum candidates to report (1-50, default 20). [kind=name] Maximum results per list (1–20, default 5): with type 'any' you get up to `limit` books AND up to `limit` authors. (kind=book|heading|author|work|name|period only.)"
},
"offset": {
"type": "integer",
"minimum": 0,
"description": "Number of results to skip for pagination. Use with `limit` to page through large result sets. The response includes `has_more` and `next_offset` to drive paging. Default: 0. (kind=book|heading|author|period only.)"
},
"response_format": {
"type": "string",
"enum": [
"markdown",
"json",
"none"
],
"description": "Format of the human-readable text: 'markdown' (default) for display; 'json' the same data as one JSON code block; 'none' only a one-line pointer, leaving structuredContent as the whole answer. structuredContent is always returned, so a caller that parses it should pass 'none': the character budget counts both channels, and the unread copy otherwise spends most of it. Requires the 'response_format.none' capability (see shamela_get_brief)."
},
"include_counts": {
"type": "boolean",
"description": "If true (default), include book_count for each category; false returns book_count 0. Costs nothing either way — counts are precomputed. downloaded_count is always filled. (kind=category only.)"
},
"downloaded_only": {
"type": "boolean",
"description": "[kind=category] If true, list only categories with at least one book installed on this server. Useful to see where the installed library is concentrated across the 41 categories. Default: false. [kind=period] If true, restrict to books whose text is installed on this server — the only ones with searchable page content. Default false lists the whole catalogue. (kind=category|period only.)"
},
"title": {
"type": "string",
"minLength": 1,
"description": "The work's title as cited, e.g. «صحيح البخاري», «المغني», «فتح الباري». Leading words only — the title, not a description of it. You may include the catalogue's own edition tail, with the « - » («صحيح البخاري - ط السلطانية») or without it («مسند أحمد ط الرسالة»), and it will be read as `edition`. An author name appended instead («صحيح البخاري محمد بن إسماعيل البخاري», «الهداية للمرغيناني») is read as `author`. Where a title is held by more than one author, a trailing edition is NOT read as one until you also pass `author` — «صحيح البخاري ت البغا» alone is refused, «صحيح البخاري ت البغا» + author «البخاري» resolves. The title must be a PREFIX of the entry's own: «شرح صحيح البخاري», «المدخل إلى صحيح البخاري» and «عمدة القاري شرح صحيح البخاري» contain the work's name and none IS the work. A commentary is rejected (listed in rejected[]): quoting it as the work would attribute its author's words to al-Bukhari. Example: {title:'المغني', author:'ابن قدامة'}. (kind=work only.)"
},
"author": {
"type": "string",
"description": "The author as cited, when known. This is a FILTER, not a hint: a candidate whose author cannot be reconciled with it is rejected rather than ranked down, because that is exactly how a commentary gets returned as the work it comments on. (kind=work only.)"
},
"edition": {
"type": "string",
"description": "A specific printing, as the catalogue names it: 'ط السلطانية', 'ت البغا', 'ط التأصيل'. If no entry carries it you get status='not_found' with reason='edition_not_found' and the editions that DO exist — never a different book. (kind=work only.)"
},
"type": {
"type": "string",
"enum": [
"any",
"book",
"author"
],
"description": "'book' matches catalogue titles only, 'author' author names only, 'any' (default) both, returned as separate books[] and authors[] lists. Reads the s_book/ and s_author/ n-gram indexes (fast partial matching). Example: {query:'ابن قدامة', type:'author'}. (kind=name only.)"
},
"composed_from": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive LOWER bound on book.book_date — Shamela's «تاريخ الكتاب». NOT an independent composition year: it holds the main author's death year on 98.5% of dated books (8,461/8,587), so pairing it with died_* almost never narrows anything. Pair with composed_to. (kind=period only.)"
},
"composed_to": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive UPPER bound on book.book_date («تاريخ الكتاب»; see composed_from — it is not a composition year). Pair with composed_from. (kind=period only.)"
},
"died_from": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive LOWER bound on the MAIN AUTHOR's death year (author.death_number). A different column from composed_*, but the same number on 98.5% of dated books. Pair with died_to. At least one of composed_from/composed_to/died_from/died_to is required (BAD_ARG); a missing side is open-ended, and undated books drop out under any bound set. E.g. died_from:700, died_to:800 = 8th-century-Hijri authors. A composed-vs-died question («written in the 8th century by authors who died in the 9th») cannot be answered from this data: a near-empty result means the distinction is unavailable, not that no such book exists. (kind=period only.)"
},
"died_to": {
"type": "integer",
"minimum": 1,
"maximum": 2000,
"description": "Hijri year, inclusive UPPER bound on the MAIN AUTHOR's death year (author.death_number). Pair with died_from. (kind=period only.)"
},
"category_id": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "Optional: restrict to one category (use shamela_find(kind='category') for IDs). Categories are flat in master.db. Example: {died_from:600, died_to:700, category_id:17}. (kind=period only.)"
},
"kind": {
"type": "string",
"enum": [
"book",
"heading",
"author",
"category",
"work",
"name",
"period"
],
"description": "Selects what the call does: book | heading | author | category | work | name | period. Default book."
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}