{"openapi":"3.1.0","info":{"title":"UTME Lab Question Bank API","description":"Every candidate writes English plus three others. Questions are keyed to subtopics so a weak spot is a 20-question sitting, not a 40-question paper. Explanations are Markdown and carry the trap that catches people.","license":{"name":""},"version":"0.1.0"},"servers":[{"url":"https://api.utmelab.com","description":"Production"}],"paths":{"/api/v1/exams":{"get":{"tags":["catalog"],"operationId":"listExams","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamList"}}}}}}},"/api/v1/exams/{exam}/papers":{"get":{"tags":["papers"],"operationId":"getExamScheme","parameters":[{"name":"exam","in":"path","description":"Exam code, as listed by GET /exams.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaperReadiness"}}}},"404":{"description":"No subject carried for that exam"}}}},"/api/v1/exams/{exam}/subjects":{"get":{"tags":["catalog"],"operationId":"listSubjects","parameters":[{"name":"exam","in":"path","description":"Exam code, e.g. JAMB","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamSubjects"}}}},"404":{"description":"No such exam"}}}},"/api/v1/keys/me":{"get":{"tags":["account"],"description":"Returns the calling key exactly as stored. Use it to check a key works.","operationId":"whoami","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Missing or invalid API key"},"429":{"description":"Over the key's per-minute limit"}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/novels":{"get":{"tags":["novels"],"description":"All novels, newest prescription first. No key required.","operationId":"listNovels","parameters":[{"name":"exam","in":"query","description":"Scope to an exam.","required":false,"schema":{"type":"string"}},{"name":"year","in":"query","description":"Prescription year, e.g. 2025.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"featured","in":"query","description":"Only the current set text.","required":false,"schema":{"type":"boolean"}},{"name":"category","in":"query","description":"english (reading text) or literature (prescribed text).","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NovelList"}}}}}}},"/api/v1/novels/{id}":{"get":{"tags":["novels"],"description":"One novel: book, chapters, characters, themes. No key required.","operationId":"getNovel","parameters":[{"name":"id","in":"path","description":"Novel id, e.g. lekki-headmaster.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NovelDoc"}}}},"404":{"description":"No such novel"}}}},"/api/v1/novels/{id}/questions":{"get":{"tags":["novels"],"description":"Quiz items for one novel, paged. The answer is withheld unless asked.","operationId":"listNovelQuestions","parameters":[{"name":"id","in":"path","description":"Novel id.","required":true,"schema":{"type":"string"}},{"name":"chapter","in":"query","description":"Drill one chapter.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"include_explanation","in":"query","description":"Include the answer and the working. Default: false.","required":false,"schema":{"type":"boolean"}},{"name":"page","in":"query","description":"1-based page number. Default: 1.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"per_page","in":"query","description":"Page size. Default: 50. Capped at 200.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NovelQuestionPage"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"No such novel"},"429":{"description":"Over the key's per-minute limit"}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/passages/{id}":{"get":{"tags":["catalog"],"description":"One shared stimulus — a comprehension or cloze passage its questions point at.","operationId":"getPassage","parameters":[{"name":"id","in":"path","description":"Passage id.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassageEnvelope"}}}},"404":{"description":"No such passage"}}}},"/api/v1/questions":{"get":{"tags":["questions"],"description":"Paged, and the answer is withheld unless you ask for it.\n\n**The answer is only in the response when `include_explanation=true`.** A quiz consumer needs the stem first, and an answer in a default payload gets rendered by somebody, eventually, by accident.\n\nRender option labels from array position: ids are stable identity, labels are display.","operationId":"listQuestions","parameters":[{"name":"subtopic","in":"query","description":"Drill one subtopic.","required":false,"schema":{"type":"string"}},{"name":"exam","in":"query","description":"Scope to an exam.","required":false,"schema":{"type":"string"}},{"name":"subject","in":"query","description":"Scope to a subject within that exam.","required":false,"schema":{"type":"string"}},{"name":"verified","in":"query","description":"Only human-checked questions. Default: no filter.","required":false,"schema":{"type":"boolean"}},{"name":"year","in":"query","description":"Only questions prepared for this exam year. Default: no filter.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"source","in":"query","description":"Provenance: `past_paper`, `authored` or `imported`. Default: no filter.","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring of the question text, case-insensitive. Unranked — results stay in id order. Default: no filter.","required":false,"schema":{"type":"string"}},{"name":"include_explanation","in":"query","description":"Include the answer and the working. Default: false.","required":false,"schema":{"type":"boolean"}},{"name":"page","in":"query","description":"1-based page number. Default: 1.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"per_page","in":"query","description":"Page size. Default: 50. Capped at 200.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionPage"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"No such subject, or no usable filter"},"429":{"description":"Over the key's per-minute limit"}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/questions/{id}":{"get":{"tags":["questions"],"summary":"One question, addressed by the id we handed out.","description":"One question by id, shaped exactly like an item from `/questions`.\n\nThe answer is withheld unless `include_explanation=true`, the same rule the list follows.","operationId":"getQuestion","parameters":[{"name":"id","in":"path","description":"A question id, exactly as returned by `/questions`.","required":true,"schema":{"type":"string"}},{"name":"include_explanation","in":"query","description":"Include the answer and the working. Default: false.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderedQuestion"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"No question with that id"}}}},"/api/v1/questions/{id}/report":{"post":{"tags":["questions"],"description":"Flag a broken question. Reasons are a closed set so reports aggregate; free-text detail is optional and capped. Reports never change the bank — they join a review queue, and a question with many flags gets human eyes.","operationId":"reportQuestion","parameters":[{"name":"id","in":"path","description":"Question id.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportBody"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportReceipt"}}}},"400":{"description":"Unknown reason, or detail over 500 characters"},"401":{"description":"Missing or invalid API key"},"404":{"description":"No such question"},"429":{"description":"Over a limit — per-minute or the daily report cap"}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/subjects/{exam}/{slug}":{"get":{"tags":["catalog"],"operationId":"getSubject","parameters":[{"name":"exam","in":"path","description":"Exam code.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","description":"The exam's own name for this subject.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubjectEnvelope"}}}},"404":{"description":"No such subject in that exam"}}}},"/api/v1/subjects/{exam}/{slug}/coverage":{"get":{"tags":["catalog"],"summary":"Coverage without the tree: counts only, for progress bars and checklists.","description":"How much of this subject you can practise — counts only, no topic tree.","operationId":"getCoverage","parameters":[{"name":"exam","in":"path","description":"Exam code.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","description":"Subject slug within that exam.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Coverage"}}}},"404":{"description":"No such subject in that exam"}}}},"/api/v1/subjects/{exam}/{slug}/syllabus":{"get":{"tags":["catalog"],"description":"The whole topic tree for one subject, with a question count and objectives on every node — what you must be able to do, beside what you can actually practise. A topic with no questions does not appear.","operationId":"getSyllabus","parameters":[{"name":"exam","in":"path","description":"Exam code.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","description":"Subject slug within that exam.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Syllabus"}}}},"404":{"description":"No such subject in that exam"}}}},"/api/v1/topics/{exam}/{subject}/{slug}":{"get":{"tags":["catalog"],"description":"One topic with its subtopics, each carrying its question count and objectives.","operationId":"getTopic","parameters":[{"name":"exam","in":"path","description":"Exam code.","required":true,"schema":{"type":"string"}},{"name":"subject","in":"path","description":"Subject slug within that exam.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","description":"Topic slug.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicDetail"}}}},"404":{"description":"No such topic"}}}},"/health":{"get":{"tags":["meta"],"summary":"`/health` and `/openapi.json` are real handlers elsewhere; these carry only the\ndocumentation, so the contract can name them without a route item here.","description":"Unauthenticated. Reports question and withheld-record counts.","operationId":"health","responses":{"200":{"description":"Service is up"}}}},"/openapi.json":{"get":{"tags":["meta"],"description":"This document.","operationId":"openapi","responses":{"200":{"description":"The OpenAPI description"}}}}},"components":{"schemas":{"ApiKey":{"type":"object","description":"A key as stored. Deliberately carries no secret material — no plaintext,\nno hash. `key_prefix` (first 8 chars of `sk_…`) is for support lookup only\nand never authenticates.","required":["id","label","rate_limit","created_at"],"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"expires_at":{"type":["string","null"]},"id":{"type":"string"},"key_prefix":{"type":["string","null"]},"label":{"type":"string"},"last_used_at":{"type":["string","null"]},"plan":{"type":"string"},"quota_monthly":{"type":["integer","null"],"format":"int32","minimum":0},"rate_limit":{"type":"integer","format":"int32","description":"Per-key request allowance (D26) — a column, not a key *type*.","minimum":0},"revoked_at":{"type":["string","null"]},"scopes":{"type":"string"},"suspend_reason":{"type":["string","null"]},"suspended_at":{"type":["string","null"]}}},"Choice":{"type":"object","description":"One answer choice. Named `Choice` because `Option` is Rust's, not ours.\n\n`id` is stable identity; the **label** the student sees (A, B, C…) is\nassigned from array position at render time and is never stored (D15).","required":["id","text"],"properties":{"id":{"type":"string"},"text":{"type":"string"}}},"Coverage":{"type":"object","description":"Counts without the tree — for progress bars and checklists.","required":["exam","subject","summary"],"properties":{"exam":{"type":"string"},"subject":{"$ref":"#/components/schemas/Subject"},"summary":{"$ref":"#/components/schemas/SyllabusSummary"}}},"Exam":{"type":"object","required":["code","subject_count"],"properties":{"code":{"type":"string","description":"Exam code, e.g. `JAMB`."},"subject_count":{"type":"integer","minimum":0}}},"ExamList":{"type":"object","required":["exams"],"properties":{"exams":{"type":"array","items":{"$ref":"#/components/schemas/Exam"}}}},"ExamSubjects":{"type":"object","description":"One exam's subject list.","required":["exam","subjects"],"properties":{"exam":{"type":"string"},"subjects":{"type":"array","items":{"$ref":"#/components/schemas/Subject"}}}},"Explanation":{"type":"object","description":"Why the answer is what it is (D17). `key_point` is the flashcard teaser,\n`body` the working, `trap` the distractor that catches people.","required":["key_point","body"],"properties":{"body":{"type":"string"},"key_point":{"type":"string"},"trap":{"type":["string","null"]}}},"Novel":{"type":"object","description":"A prescribed text. One row per (exam, cycle) — the novel rotates yearly, so\n`year_prescribed` is load-bearing, not decorative.","required":["id","title","author","exam","synopsis","setting_place","setting_period"],"properties":{"author":{"type":"string"},"category":{"type":"string","description":"Which paper sets it: the Use-of-English reading text, or a\nLiterature-in-English prescribed text. Consumers filter on this."},"cover_image":{"type":["string","null"]},"exam":{"type":"string"},"featured":{"type":"boolean","description":"At most one per exam — enforced at load. The current set text."},"id":{"type":"string"},"setting_period":{"type":"string"},"setting_place":{"type":"string"},"synopsis":{"type":"string"},"title":{"type":"string"},"year_prescribed":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Every cycle this is (or was) the set text for. A novel routinely spans\nmore than one UTME — *The Lekki Headmaster* covers 2025 and 2026."},"year_published":{"type":["integer","null"],"format":"int32","description":"May be unknown for older set texts — absent, not zero.","minimum":0}}},"NovelChapter":{"type":"object","required":["number","title"],"properties":{"key_events":{"type":"array","items":{"type":"string"}},"number":{"type":"integer","format":"int32","minimum":0},"summary":{"type":["string","null"],"description":"Null means not yet written — absence is the signal (D16)."},"title":{"type":"string"}}},"NovelCharacter":{"type":"object","description":"Character types use JAMB's own vocabulary (round/flat/foil/hero/antihero/\nvillain/heroine) because that is literally what the syllabus tests.","required":["name","role","character_type","description"],"properties":{"character_type":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"}}},"NovelDoc":{"allOf":[{"$ref":"#/components/schemas/Novel"},{"type":"object","properties":{"chapters":{"type":"array","items":{"$ref":"#/components/schemas/NovelChapter"}},"characters":{"type":"array","items":{"$ref":"#/components/schemas/NovelCharacter"}},"questions":{"type":"array","items":{"$ref":"#/components/schemas/NovelQuestion"}},"themes":{"type":"array","items":{"$ref":"#/components/schemas/NovelTheme"}}}}],"description":"One file in `data/novels/`. The directory is the index — no separate list\nto keep in sync. Dropping a novel is deleting its file."},"NovelList":{"type":"object","required":["novels"],"properties":{"novels":{"type":"array","items":{"$ref":"#/components/schemas/Novel"}}}},"NovelQuestion":{"type":"object","description":"A quiz item about the book. Reuses Option/answer/Explanation machinery —\na novel question is answered, shuffled and explained exactly like a subject\nquestion. It points at a chapter, not a subtopic, and the novel is the\npassage, so there is no passage_id.","required":["id","text","options","answer","verified"],"properties":{"answer":{"type":"string","description":"An option id, same contract as Question.answer (D15)."},"chapter_number":{"type":["integer","null"],"format":"int32","minimum":0},"explanation":{"oneOf":[{"$ref":"#/components/schemas/Explanation"},{"type":"null"}]},"id":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/Choice"}},"text":{"type":"string"},"verified":{"type":"boolean"}}},"NovelQuestionPage":{"type":"object","required":["questions","page","per_page","total_pages","total_records","includes_explanation"],"properties":{"includes_explanation":{"type":"boolean"},"page":{"type":"integer","format":"int32","minimum":0},"per_page":{"type":"integer","format":"int32","minimum":0},"questions":{"type":"array","items":{"$ref":"#/components/schemas/RenderedNovelQuestion"}},"total_pages":{"type":"integer","format":"int32","minimum":0},"total_records":{"type":"integer","minimum":0}}},"NovelTheme":{"type":"object","required":["name","description"],"properties":{"description":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}},"name":{"type":"string"}}},"PaperReadiness":{"type":"object","description":"The scheme plus whether the bank can fill it. Readiness is kept beside the\nscheme rather than inside it, so the shape a consumer validates does not\nmove when our content grows.","required":["exam","scheme","complete"],"properties":{"complete":{"type":"boolean","description":"True when every compulsory subject plus three electives can fill their\nfull question count with verified questions."},"exam":{"type":"string"},"scheme":{"$ref":"#/components/schemas/Scheme"}}},"Passage":{"type":"object","description":"A stimulus shared by many questions — one comprehension passage serving five\nquestions, one cloze serving ten.","required":["id","exam","subject","year","kind","text"],"properties":{"author":{"type":["string","null"]},"exam":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"subject":{"type":"string"},"text":{"type":"string"},"title":{"type":["string","null"]},"year":{"type":"integer","format":"int32","minimum":0}}},"PassageEnvelope":{"type":"object","required":["passage"],"properties":{"passage":{"$ref":"#/components/schemas/Passage"}}},"QuestionPage":{"type":"object","description":"The paged response envelope (D3/D30).","required":["questions","page","per_page","total_pages","total_records","includes_explanation"],"properties":{"includes_explanation":{"type":"boolean","description":"Whether `answer` and `explanation` were included (D27)."},"page":{"type":"integer","format":"int32","description":"1-based.","minimum":0},"per_page":{"type":"integer","format":"int32","minimum":0},"questions":{"type":"array","items":{"$ref":"#/components/schemas/RenderedQuestion"}},"total_pages":{"type":"integer","format":"int32","minimum":0},"total_records":{"type":"integer","minimum":0}}},"RenderedNovelQuestion":{"type":"object","required":["id","novel_id","text","options","verified"],"properties":{"answer":{"type":["string","null"]},"chapter_number":{"type":["integer","null"],"format":"int32","minimum":0},"explanation":{"oneOf":[{"$ref":"#/components/schemas/Explanation"},{"type":"null"}]},"id":{"type":"string"},"novel_id":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/Choice"}},"text":{"type":"string"},"verified":{"type":"boolean"}}},"RenderedQuestion":{"type":"object","required":["id","exam","subject","year","source","text","options","marks","difficulty","verified"],"properties":{"answer":{"type":["string","null"],"description":"Present only when explanations were requested."},"difficulty":{"type":"integer","format":"int32","minimum":0},"exam":{"type":"string"},"explanation":{"oneOf":[{"$ref":"#/components/schemas/Explanation"},{"type":"null"}]},"id":{"type":"string"},"image":{"type":["string","null"]},"marks":{"type":"number","format":"float"},"options":{"type":"array","items":{"$ref":"#/components/schemas/Choice"}},"section":{"type":["string","null"]},"source":{"type":"string"},"subject":{"type":"string"},"text":{"type":"string"},"verified":{"type":"boolean"},"year":{"type":"integer","format":"int32","minimum":0}}},"ReportBody":{"type":"object","required":["reason"],"properties":{"detail":{"type":["string","null"],"description":"Optional context, max 500 characters. No PII — never send names or emails."},"reason":{"type":"string","description":"One of: wrong_answer, unclear_stem, broken_option, wrong_explanation, other."}}},"ReportReceipt":{"type":"object","required":["id","question_id","reason","duplicate"],"properties":{"duplicate":{"type":"boolean","description":"True when this exact flag was already filed — POST is idempotent here."},"id":{"type":"string"},"question_id":{"type":"string"},"reason":{"type":"string"}}},"Scheme":{"type":"object","description":"The exam's shape, derived from the subjects we actually carry.","required":["exam","subjects","rules","questions_available","subjects_ready"],"properties":{"duration_minutes":{"type":["integer","null"],"format":"int32","description":"`None` where unverified — absent, not zero (same rule as `year_published`).","minimum":0},"exam":{"type":"string"},"questions_available":{"type":"integer","format":"int32","description":"How many questions a full paper contains, summed from what we carry.\nThis is a lower bound for any exam whose bank is incomplete — an English\npaper only counts if `use-of-english` has 60 real questions.","minimum":0},"rules":{"type":"array","items":{"type":"string"},"description":"Human-readable caveats a candidate or consumer must know. The WAEC\n\"answer 10 of 13\" problem (recorded, not solved) lives here."},"subjects":{"type":"array","items":{"$ref":"#/components/schemas/SubjectSlot"}},"subjects_ready":{"type":"array","items":{"type":"string"},"description":"Which subjects can actually fill a slot today."}}},"Subject":{"type":"object","description":"One exam's offering of a subject (D21). The same concept can appear once per\nexam, carrying that board's own name — `Use of English` and\n`English Language` are two true rows, not one row with an alias.","required":["slug","exam","name","is_compulsory","full_exam_questions"],"properties":{"exam":{"type":"string"},"full_exam_questions":{"type":"integer","format":"int32","minimum":0},"is_compulsory":{"type":"boolean"},"name":{"type":"string"},"slug":{"type":"string"}}},"SubjectEnvelope":{"type":"object","description":"A single subject, wrapped so the response is one shape rather than a bare\nobject or a one-key envelope depending on the caller.","required":["subject"],"properties":{"subject":{"$ref":"#/components/schemas/Subject"}}},"SubjectSlot":{"type":"object","description":"One subject's slot in an exam.","required":["slug","name","compulsory","questions"],"properties":{"compulsory":{"type":"boolean"},"marks_per_question":{"type":["number","null"],"format":"float","description":"`None` where the per-question mark is not uniform (English carries\n1–3 depending on section), so a consumer must use each row's `marks`."},"name":{"type":"string"},"questions":{"type":"integer","format":"int32","description":"How many questions this subject contributes (from `full_exam_questions`).","minimum":0},"slug":{"type":"string"}}},"Syllabus":{"type":"object","description":"The syllabus tree with coverage counts (D19).","required":["exam","subject","topics","summary"],"properties":{"exam":{"type":"string"},"subject":{"$ref":"#/components/schemas/Subject"},"summary":{"$ref":"#/components/schemas/SyllabusSummary"},"topics":{"type":"array","items":{"$ref":"#/components/schemas/SyllabusTopic"}}}},"SyllabusSubtopic":{"type":"object","required":["slug","name","order","question_count","objectives"],"properties":{"name":{"type":"string"},"objectives":{"type":"array","items":{"type":"string"}},"order":{"type":"integer","format":"int32","minimum":0},"question_count":{"type":"integer","minimum":0},"slug":{"type":"string"}}},"SyllabusSummary":{"type":"object","required":["topics","topics_with_questions","subtopics","subtopics_with_questions","questions"],"properties":{"questions":{"type":"integer","minimum":0},"subtopics":{"type":"integer","minimum":0},"subtopics_with_questions":{"type":"integer","minimum":0},"topics":{"type":"integer","minimum":0},"topics_with_questions":{"type":"integer","minimum":0}}},"SyllabusTopic":{"type":"object","required":["slug","name","order","weight","question_count","note_available","objectives","subtopics"],"properties":{"name":{"type":"string"},"note_available":{"type":"boolean","description":"Notes are shape-only until D18 is activated."},"objectives":{"type":"array","items":{"type":"string"},"description":"JAMB states objectives at topic level, so that is where they live\n(D50). Subtopics carry none rather than being given invented ones."},"order":{"type":"integer","format":"int32","minimum":0},"question_count":{"type":"integer","minimum":0},"slug":{"type":"string"},"subtopics":{"type":"array","items":{"$ref":"#/components/schemas/SyllabusSubtopic"}},"weight":{"type":"integer","format":"int32","minimum":0}}},"Topic":{"type":"object","description":"A topic within one exam's syllabus.","required":["slug","subject","exam","name","order","weight"],"properties":{"exam":{"type":"string"},"name":{"type":"string"},"objectives":{"type":"array","items":{"type":"string"},"description":"The examinable statements JAMB attaches to this topic. Objectives sit\nhere because that is the level JAMB states them at; subtopics inherit\nnone of their own rather than being given invented ones."},"order":{"type":"integer","format":"int32","minimum":0},"slug":{"type":"string"},"subject":{"type":"string"},"weight":{"type":"integer","format":"int32","description":"Marks JAMB gives this topic. **0 means \"not published\"**, never \"worth\nnothing\" — JAMB states weights for some subjects and not others, and a\nfabricated weight would reach consumers as a fact.","minimum":0}}},"TopicDetail":{"type":"object","required":["topic","subtopics"],"properties":{"subtopics":{"type":"array","items":{"$ref":"#/components/schemas/TopicSubtopic"}},"topic":{"$ref":"#/components/schemas/Topic"}}},"TopicSubtopic":{"type":"object","required":["slug","name","order","question_count","objectives"],"properties":{"name":{"type":"string"},"objectives":{"type":"array","items":{"type":"string"}},"order":{"type":"integer","format":"int32","minimum":0},"question_count":{"type":"integer","minimum":0},"slug":{"type":"string"}}}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Send your key in the `X-API-Key` header. One key type: it works from a server, an app, or curl — we never ask where it lives. If you embed it in a client app it can be extracted, and whoever extracts it shares your quota."}}},"tags":[{"name":"catalog","description":"Exams, subjects and syllabi. No key required."},{"name":"questions","description":"The metered surface. Requires a key."},{"name":"account","description":"The calling key."},{"name":"novels","description":"Prescribed texts: book, chapters, characters, themes, quiz."},{"name":"meta","description":"Service health and this document."}]}