API reference
Base URL: https://kalelm-api.mohanad.xyz/api. Collections (/materials, /transcripts, /sheikhs, …) follow Payload’s REST conventions: ?limit=&page=&depth=&where[field][equals]=&select[field]=true&locale=. Everything below is custom.
Search
Section titled “Search”POST /search — JSON body.
| Field | Values | Notes |
|---|---|---|
q |
string | required unless mode is keyword |
mode |
keyword · hybrid · semantic |
default keyword from the API; the site defaults to hybrid |
scope |
transcripts · titles |
titles is always a literal match |
sort |
relevance · date sorts |
|
type[], sheikhId[], seriesId[] |
filters | |
since |
unix seconds | results on or after |
language |
ar (default) · en · ur |
which language’s material |
limit, page |
1–50, ≥1 | transcript results page over a grouped window, not the whole archive |
Response: query, mode, degraded, matchCount, matchDocs, tookMs, scope, page, limit, totalPages, results[], facets. degraded is set to the requested mode when keyword results were served instead. matchCount/matchDocs are null for vector modes on purpose.
GET /related/:id?limit=6 — recordings close in meaning to one recording, judged by its transcript as a whole: the mean of its passage vectors queried against the passage index, no embedder call. results[] in the search result shape (with chapter); empty for a material without a transcript. Cached a day at the edge.
GET /series-summary/:id?language=ar&sheikh=ID — what a series page needs beyond its rows, scoped to one sheikh when sheikh is given (the sheikhs[] list is always the whole series, for the switch): count, duration (seconds), first/last dates, sheikhs[] with counts, chapters[] (book chapters with counts and durations, in the order they were taught: by first lesson) and months[] (YYYY-MM with counts, newest first). SQL, cached five minutes.
Learning platform
Section titled “Learning platform”Courses come through Payload’s REST: GET /courses?where[status][equals]=published&depth=1 (a course’s levels[].questions[] carry no answer or explanation for a public reader). The reader’s identity is the account hub’s access token, sent as Authorization: Bearer …; the backend verifies it at ${HUB_URL}/api/auth/oauth2/userinfo.
| Endpoint | Body / params | Answer |
|---|---|---|
POST /learn/start |
{ course, level }, bearer |
{ ticket, startedAt, seconds } — the signed start of a timed test; seconds is null for an untimed level |
POST /learn/attempt?language= |
{ course, level, answers[], ticket? } — an answer is a 1-based option number, a list of them (multiple), or a string (text); bearer |
{ correct, total, score, passed, late, passMark, level, levelsPassed[], certificate, review[] } with review[i] = { type, correct[], answerText, given, ok, feedback, explanation }; 403 with level when an earlier level is not passed; 400 without the ticket of a timed level; 401 without a valid token; 10 per minute per IP |
GET /learn/certificate/:code.pdf?lang= |
public | the certificate as a PDF (A4 landscape), cached an hour; 404 when the code is unknown or revoked |
POST /learn/ai |
{ course, level, action: 'questions' | 'summary', count? }, signed-in staff |
{ added } or { description }, written into the course; 503 without ANTHROPIC_API_KEY |
GET /learn/me?course= |
bearer | { learner, attempts[], certificates[] } |
GET /learn/verify/:code |
public | { code, learnerName, score, issuedAt, course { title, slug, sheikh } } or 404; cached five minutes at the edge |
The hub’s own API for sites (hub/): GET /api/data/:site, PUT /api/data/:site/:kind (an object of key → value), DELETE /api/data/:site/:kind/:key, all with the reader’s bearer; see Learning platform and the account hub.
GET /series-index?language=ar — every (series, sheikh) pair with published lessons in that language, one row each: id, title, slug, kind, count, duration, last, sheikh {id, name, slug}, chapters[] {id, title, slug, count} (a book explanation’s chapters in the order that sheikh taught them), newest-updated first. Feeds the lessons landing page. SQL, cached five minutes.
Facets and browse
Section titled “Facets and browse”GET /facets?type=&language=&sheikh=&series=&year=&hasTranscript=&locale=— counts by sheikh, series, year and transcript presence for the current filters. Cached 5 min per query string.GET /tree?order=type,sheikh,series&path=&language=&offset=&limit=— one level of the browse tree;?q=searches it. Cached 5 min.
Transcription pipeline
Section titled “Transcription pipeline”Admin session required for the first two; the rest accept Authorization: Bearer $CRON_SECRET (or an admin session).
| Endpoint | Body | Returns |
|---|---|---|
GET /pipeline/stats |
counts by status, backlog, last 24 h, active workers, recent failures, settings, local worker | |
POST /pipeline/control |
{action: enqueue, limit?} · retryFailed · {requeue, id} · {pause, paused} · startWorker · stopWorker |
action result |
GET /indexing/material?id= |
signed-in editor | one material’s presence in the four indexes (MaterialIndexStatus: cues, segments, sentences, passages, complete, missing) |
POST /indexing/reindex |
{id}, signed-in editor |
re-pushes the title, the segments (now) and the vectors (background) for one material |
POST /pipeline/reindex-vectors |
{ids?: number[], sinceHours?: 24} (worker secret or admin) |
{scheduled}; re-pushes sentence and passage vectors in the background |
POST /pipeline/claim |
{worker, limit?} |
{jobs: [{id, materialId, title, audioUrl, language, duration}], paused} |
POST /pipeline/heartbeat |
{id, stage?} |
{ok} — false means the job is no longer yours |
POST /pipeline/complete |
{id, cues: [{start, text}], stats?, language?} or {id, error} |
`{status: done |
Operations panels
Section titled “Operations panels”GET /durations/stats,POST /durations/run {kind: youtube|mp3}or{stop: true}— the duration backfill.GET /indexing/stats— progress of the search index builds.
All three panels are admin-only and read by /admin/durations, /admin/indexing, /admin/transcription.
Auth summary
Section titled “Auth summary”| Caller | How |
|---|---|
| Site | anonymous REST; read access is public for published content |
| Admin panel | Payload session cookie; roles admin / editor |
| Workers, cron | Authorization: Bearer $CRON_SECRET |