# tadjwid API v2 — Getting Started

Public REST API для исламской образовательной платформы [Муаллим Таджвид](https://e-replika.ru/tadjwid/). Открыта для сторонних приложений.

**Base URL:** `https://e-replika.ru/tadjwid/api/v2`

**Interactive docs:**
- Swagger UI — `https://e-replika.ru/tadjwid/api/v2/docs`
- ReDoc — `https://e-replika.ru/tadjwid/api/v2/redoc`
- OpenAPI spec — `https://e-replika.ru/tadjwid/api/v2/openapi.json`
- JSON catalogue — `https://e-replika.ru/tadjwid/api/v2/info`

> Документ актуализирован: 2026-05. Контент: 170 уроков, 171 квиз,
> 615 терминов глоссария, 400 изображений.

---

## Quick start (без ключа, anonymous)

100 запросов / час / IP. Подходит для прототипирования.

```bash
# Список первых 10 терминов глоссария
curl 'https://e-replika.ru/tadjwid/api/v2/glossary?per_page=10' | jq '.items[].term'

# Полный урок «История арабского алфавита»
curl 'https://e-replika.ru/tadjwid/api/v2/lessons/alphabet/01-history' | jq '.metadata.title'

# Все мечети как GeoJSON для Leaflet/Mapbox
curl 'https://e-replika.ru/tadjwid/api/v2/calligraphy/mosques?format=geojson' \
  | jq '.features[] | .properties.mosque'

# Транслитерация Arabic → Russian
curl -X POST 'https://e-replika.ru/tadjwid/api/v2/transliteration' \
  -H 'Content-Type: application/json' \
  -d '{"text":"بِسْمِ ٱللَّهِ","level":2,"teacherStyle":true}'
# → {"input":"بِسْمِ ٱللَّهِ","level":2,"teacherStyle":true,"result":"бисми лля́hи"}
```

---

## API key (50000 req/hour)

Для production — получите API key через админку:

```bash
# Login (только админ)
TOKEN=$(curl -s -X POST 'https://e-replika.ru/tadjwid/admin/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"password":"…"}' | jq -r .token)

# Create key (показывается plaintext один раз!)
curl -X POST 'https://e-replika.ru/tadjwid/api/v2/keys' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"my-app","scope":"read"}'
# → {"id":3,"key":"tk_abc123...","scope":"read","warning":"Save this key…"}
```

Использование:
```bash
curl -H 'X-API-Key: tk_abc123...' \
  'https://e-replika.ru/tadjwid/api/v2/glossary'
```

Self-service ключ (без админки, 1 ключ на IP в сутки, scope всегда `read`):
```bash
curl -X POST 'https://e-replika.ru/tadjwid/api/v2/keys/request' \
  -H 'Content-Type: application/json' \
  -d '{"name":"my-research-tool","ttl_days":30}'
```

---

## Endpoints (38)

### Glossary (615 терминов)
```
GET /glossary?q=&category=&sort=&page=&per_page=&fields=
GET /glossary/categories
GET /glossary/{term-id}
```

### Lessons (170 уроков: 8 alphabet + 52 tajweed + 110 quran; 171 квиз)
```
GET /lessons?q=&category=&sort=&page=&per_page=&fields=    — метаданные
GET /lessons/{cat}/{id}?section_type=                      — полный урок
GET /lessons/{cat}/{id}/section-types                      — типы секций + счётчик
GET /lessons/{cat}/{id}/quizzes                            — только квизы
GET /quizzes?category=&q=&lesson_id=&sort=&fields=         — глобальный пул
```

### Calligraphy
```
GET /calligraphy/styles?q=&sort=&page=&per_page=&fields=   — 11 стилей
GET /calligraphy/styles/{id}
GET /calligraphy/calligraphers?q=&style=&sort=             — 18 мастеров
GET /calligraphy/calligraphers/{id}
GET /calligraphy/mosques?q=&style=&sort=&format=geojson    — 37 мечетей
GET /calligraphy/mosques/{id}
```

### Quran metadata
```
GET /surahs?q=&revelation_type=&min_verses=&max_verses=&sort=   — 114 сур
GET /surahs/{n}
GET /translations                                              — список переводов
GET /translations/{id}                                         — полный перевод
GET /translations/{id}/ayah/{surah}/{ayah}                      — один аят
GET /ayah-analysis?surah=&ayah=&q=&sort=&page=&per_page=        — анализ аята
GET /srs-decks?category=&q=&sort=&fields=                       — SRS-колоды
GET /srs-decks/{deck_id}
GET /word-audio/{surah}/{ayah}/{word}                           — MP3 слова
```

### Transliteration & Tajweed
```
POST /transliteration                                     — Arabic→Russian
GET /tajweed/rules?q=&difficulty=&sort=&fields=            — правила
GET /tajweed/rules/{id}
GET /search?q=...                                         — FTS5
```

### Assets
```
GET /images?q=&category=&ext=&min_size=&max_size=&sort=   — 400 файлов
GET /images/categories
GET /media?q=&category=&ext=&min_size=&max_size=&sort=    — видео/аудио
GET /assets/stats
GET /course-structure                                     — навигация SPA
GET /info                                                 — каталог эндпойнтов
```

### Admin (JWT auth)
```
POST   /keys                                  — создать ключ
GET    /keys                                  — список
DELETE /keys/{id}                             — revoke (soft)
DELETE /keys/{id}/permanent                   — удалить навсегда
GET    /keys/{id}/usage?days=7                — статистика ключа
GET    /stats?days=7                          — глобальный дэшборд трафика
```

---

## Глобальные query-параметры

Эти параметры поддерживаются всеми коллекционными эндпойнтами (если применимы).

### Пагинация
```
?page=2&per_page=50      — page ≥ 1, per_page 1..200 (default 50)
```
Ответ коллекции:
```json
{"items": [...], "page": 1, "per_page": 50, "total": 615, "pages": 13}
```

### Поиск и фильтры
```
?q=хадис                 — подстрочный поиск по текстовым полям
?category=tajweed        — фильтр по категории
?fields=term,arabic      — проекция: вернуть только указанные поля
?sort=title              — сортировка по полю (возр.)
?sort=-order             — сортировка по полю (убыв., префикс «-»)
?min_verses=&max_verses= — числовой диапазон (surahs — по числу аятов)
?min_size=&max_size=     — числовой диапазон (images/media — байты)
```
Неизвестное поле в `?sort` → `HTTP 422`.

### `?format` — форма текстовых полей (глобально для всех `/api/v2/*`)

Поля `content`, `note`, `definition`, `caption`, `subtitle`,
`description`, `cite_text`, `explanation`, `alt` могут отдаваться в трёх
видах:

| `?format` | Тип значения | Назначение |
|-----------|--------------|------------|
| `html` (default) | строка с HTML | прямой рендер в браузере |
| `text` | строка plain-text | поиск, индексация, превью |
| `tree` | массив узлов дерева | рендер на React/Vue/mobile без парсинга HTML |

`tree`-узел: `{type, value?, children?, href?, src?, class?, id?, data-*?}`.
Текстовый узел — `{type:"text", value:"..."}`. Пример таблицы в `tree`:
```json
[{"type":"table","children":[{"type":"thead","children":[...]}]}]
```

### `?section_type` — фильтр секций урока
Только для `GET /lessons/{cat}/{id}`. Список типов через запятую:
```
GET /lessons/quran/01-kawthar?section_type=quiz           — только квизы
GET /lessons/quran/01-kawthar?section_type=text,heading   — текст и заголовки
GET /lessons/quran/01-kawthar?section_type=text&format=tree  — чистый JSON по типу
```
Без параметра возвращаются все секции. Узнать доступные типы:
`GET /lessons/{cat}/{id}/section-types`.

### `?format=geojson`
Только для `/calligraphy/mosques` — отдаёт GeoJSON FeatureCollection с
координатами для карт (Leaflet/Mapbox), без пагинации.

---

## Структура урока (lesson JSON)

`GET /lessons/{category}/{id}` возвращает объект:

```json
{
  "metadata":   { ... },
  "sections":   [ ... ],
  "integration": { ... }
}
```

### `metadata`
```json
{
  "id": "01-kawthar",
  "category": "quran",          // alphabet | tajweed | quran
  "title": "Урок 1: Аль-Кавсар",
  "subtitle": "108 сура · 3 аята",
  "order": 1,
  "description": "Полный разбор суры…"
}
```

### `sections` — массив, полиморфен по полю `type`

Каждая секция — объект с обязательным `type`. Основные типы:

| `type` | Ключевые поля | Назначение |
|--------|---------------|------------|
| `heading` | `level` (1–6), `text`, опц. `highlight_letter` | заголовок раздела |
| `text` | `content` (HTML) | абзац текста |
| `html` | `content` (HTML) | произвольный HTML-блок (обычно таблица) |
| `alert` | `content`, `variant` (`info`/`tip`/`warning`/`success`) | выделенный блок-подсказка |
| `list` | `items` (массив), `style` | маркированный/нумерованный список |
| `table` | `headers`, `rows` | структурированная таблица |
| `term-grid` | `terms` (массив) | сетка терминов |
| `quiz` | `questions` (массив) | встроенный тест |
| `illustration` | `url`, `caption`, `alt`, `source`, `license` | изображение |
| `interactive-ayah` | `surah`, `ayah`, опц. `note` | аят с аудио и таджвид-разметкой |
| `quran-words` | `words` (массив) | словарные карточки суры |
| `letter-audio`, `letter-forms`, `letter-animation` | `letter`, `forms` | модули по букве |
| `interactive-diagram`, `video-embed`, `audio-demo` | `src`/спец. поля | мультимедиа |

**Heading унифицирован:** у каждой `heading`-секции всегда есть `level`
(целое 1–6) и `text` (строка). Поля `content` у heading нет.

### `quiz`-секция
```json
{
  "type": "quiz",
  "questions": [
    {
      "question": "От какого письма произошёл арабский алфавит?",
      "options": [
        { "id": "a", "text": "Греческого" },
        { "id": "b", "text": "Набатейского" },
        { "id": "c", "text": "Египетского" },
        { "id": "d", "text": "Китайского" }
      ],
      "correctAnswer": "b",
      "explanation": "Правильный ответ: Набатейского."
    }
  ]
}
```
- `options` — массив объектов `{ id, text }`. `id` — стабильный slug
  варианта (`a`, `b`, `c`, …).
- `correctAnswer` — строковый `id` правильного варианта (НЕ числовой
  индекс). Стабилен при переупорядочивании опций.

### `integration` — служебное поле (build-провенанс)

```json
"integration": {
  "module_1": {
    "integrated_at": "2026-01-19T10:30:04.763858",
    "sections": ["all"],
    "position": "append"
  }
}
```

Ключ записи — `module_<N>`, где `N` — номер **модуля энциклопедии
таджвида** (1–16). Это пронумерованные главы исходной Markdown-
энциклопедии (распарсенные версии — `data/encyclopedia/module_<N>_parsed.json`);
инструмент сборки `tools/lesson_enricher.py` вливает их секции в уроки.
Например: `module_1` — «Природа звука и метод Мушафаха», `module_5` —
«Обзор 7 школ чтения», `module_10` — «Методология передачи иснада».

`integration` фиксирует, какой модуль и каким образом влит в урок:

| Поле модуля | Значение |
|-------------|----------|
| `integrated_at` | ISO-таймштамп момента вливания при сборке |
| `sections` | какие секции модуля влиты (`["all"]` — все) |
| `position` | способ: `append` / `replace` / `after` |
| `sections_count` | кол-во влитых секций (присутствует не всегда) |

Это **чисто диагностическое поле** — для рендера урока на сайте и для
сторонних клиентов оно не нужно. Может быть пустым `{}` (модули не
вливались отдельно) или вовсе отсутствовать у старых уроков. Сторонним
приложениям поле можно игнорировать.

---

## Conventions

### Response headers
```
Cache-Control: public, max-age=300
X-RateLimit-Remaining: 99
X-RateLimit-Scope: anonymous
Access-Control-Allow-Origin: *
Content-Encoding: gzip       — автоматически если Accept-Encoding: gzip
```

### Errors
```json
{"detail": "Term 'xyz' not found"}                    HTTP 404
{"detail": "Invalid request …"}                       HTTP 400
{"detail": "…"}  + ValidationError schema             HTTP 422
{"detail": "Anonymous rate limit exceeded …"}         HTTP 429
{"detail": "Invalid or revoked API key"}              HTTP 401
```

---

## Examples

### JavaScript (fetch) — урок в виде дерева
```javascript
async function getLessonTree(cat, id) {
  const r = await fetch(
    `https://e-replika.ru/tadjwid/api/v2/lessons/${cat}/${id}?format=tree`,
    { headers: { 'X-API-Key': 'tk_abc...' } }
  );
  return await r.json();   // content-поля — массивы узлов дерева
}
```

### Python (requests) — квизы урока
```python
import requests
r = requests.get(
    'https://e-replika.ru/tadjwid/api/v2/lessons/quran/01-kawthar/quizzes',
    headers={'X-API-Key': 'tk_abc...'}
)
for quiz in r.json()['quizzes']:
    for q in quiz['questions']:
        correct = next(o['text'] for o in q['options']
                        if o['id'] == q['correctAnswer'])
        print(q['question'], '→', correct)
```

### cURL — Leaflet карта мечетей
```bash
curl -s 'https://e-replika.ru/tadjwid/api/v2/calligraphy/mosques?format=geojson' \
  > mosques.geojson
```

---

## Rate limits

| Type | Limit |
|------|-------|
| Anonymous (no key) | 500 req/hour per IP |
| `read` scope | 50,000 req/hour per key |
| `write` / `admin` scope | 250,000 req/hour per key |

При превышении: `429 Too Many Requests` + `Retry-After: 3600`.

## Caching

Responses кэшируются 5 мин (`Cache-Control: public, max-age=300`).
JSON-ответы автоматически сжимаются gzip.

## CORS

`Access-Control-Allow-Origin: *` для всех `/api/v2/*` — можно вызывать из
любого браузера.

---

## Каталоги (task `api-full-coverage`)

Источник истины для всех «известных платформе» сущностей вынесен в
самостоятельные read-only эндпоинты — сторонний клиент больше не должен
парсить `js/services/audio.js`, `js/components/font-switcher.js` или
`mushaf-settings-panel.js`.

### `GET /api/v2/reciters`

Каталог чтецов (20 шт.). Каждая запись содержит:

```json
{
  "id": "Alafasy_128kbps",
  "name_ru": "Мишари аль-Афаси",
  "name_ar": "مشاري راشد العفاسي",
  "name_en": "Mishary Rashid Alafasy",
  "country": "Кувейт",
  "bitrates": ["64", "128"],
  "is_default": true,
  "source": "everyayah",
  "fallback_to": "Alafasy_64kbps",
  "mp3_url_pattern": "https://everyayah.com/data/Alafasy_128kbps/{surah:03d}{ayah:03d}.mp3",
  "has_word_timings": true
}
```

- `is_default=true` — ровно у одного чтеца (`Alafasy_128kbps`).
- `mp3_url_pattern` — готовый шаблон, подставьте `{surah:03d}` и
  `{ayah:03d}` через любой `format`-движок.
- `GET /api/v2/reciters/{id}` — детальная карточка (+ био из урока
  `00c-great-reciters`, если совпадает по имени).
- `GET /api/v2/reciters/{id}/fallback` — формализованный каскад
  fallback-чтецов (та же логика, что в `audio.js`):

```json
{
  "reciter": "Sildinsky_local",
  "chain": [
    {"reciter": "Saood_ash-Shuraym_128kbps",
     "reason": "Эта сура ещё не записана Сильдинским"},
    {"reciter": "Alafasy_128kbps", "reason": "Резерв: аш-Шурайм недоступен"},
    {"reciter": "Alafasy_64kbps",  "reason": "Резерв: пониженное качество"}
  ]
}
```

### `GET /api/v2/mushaf-editions`

Редакции мусхафа (постраничные изображения). Сейчас две: `fahd` (Король
Фахд, default) и `madina` (Мадина). Поля `image_path` + `image_pattern`
дают полный путь к странице: `{image_path}{image_pattern}` →
`/media/mushaf-pages-fahd/001.jpg`.

### `GET /api/v2/fonts`

Каталог арабских шрифтов (10 шт.): `uthmanic` (default), `naskh`,
`noto-naskh`, `kufi`, `noto-kufi`, `thuluth`, `diwani`, `maghribi`,
`nastaliq`, `ruqaa`. Каждая запись — `family` (CSS font-family),
`body_class` (класс на `<body>` для активации) и `files[]` (Google
Fonts CSS или локальные @font-face).

### Прокси для прямо-читаемых файлов

| Endpoint                                       | Source                                      |
| ---------------------------------------------- | ------------------------------------------- |
| `GET /api/v2/tajweed-overrides`                | `data/tajweed-overrides.json`               |
| `GET /api/v2/transliteration/teacher-dict`     | `data/teacher-transliteration.json`         |
| `GET /api/v2/wbw/{surah}`                      | `data/wbw/{surah}.json`                     |
| `GET /api/v2/wbw/{surah}/{ayah}/{word}`        | одно слово (404 если нет)                   |

### Реестр настроек

| Endpoint                                | Описание                                       |
| --------------------------------------- | ---------------------------------------------- |
| `GET /api/v2/settings`                  | Все известные ключи (`scope=app` + `user`)     |
| `GET /api/v2/settings?scope=app`        | Только глобальные (управляются админом)        |
| `GET /api/v2/settings/{key}`            | Значение по ключу (или 404)                    |
| `POST /api/v2/settings/{key}` (admin)   | Обновление, делегирует в `settings_routes`     |

`user`-scope-настройки декларируются API (имя/описание/дефолт), но
хранятся только в `localStorage` клиента — API их не пишет.

## Bundle для рендера аята

### `GET /api/v2/ayah/{surah}/{ayah}`

Агрегатор всех данных, которые нужны для рендера одного аята. Один
запрос вместо 5–7 (раньше клиент собирал сам из `data/*.json`,
Quran.com и захардкоженных констант).

```json
{
  "surah": 108,
  "ayah":  1,
  "arabic_uthmani": "إِنَّا أَعْطَيْنَاكَ الْكَوْثَرَ",
  "arabic_simple":  "...",
  "translations": {
    "kuliev":      "Мы даровали тебе аль-Каусар ...",
    "osmanov":     "Воистину, Мы даровали тебе ...",
    "krachkovsky": "Поистине, Мы даровали тебе обильный!"
  },
  "words": [
    {"position": 1, "translation_ru": "Поистине, Мы",
     "uthmani": null, "simple": null, "transliteration": null, "audio_url": null},
    ...
  ],
  "tajweed_html":      null,
  "tajweed_analysis":  null,
  "word_timing":       null,
  "audio_urls": {
    "Alafasy_128kbps": "https://everyayah.com/data/Alafasy_128kbps/108001.mp3",
    "Husary_128kbps":  "https://everyayah.com/data/Husary_128kbps/108001.mp3",
    ...
  },
  "transliteration": {
    "L1": null, "L2": null, "L3": null,
    "teacher": "инна̃ а'т̣айна́ка лькавс̱ар"
  }
}
```

**Поведение:**

- Структура **стабильна**: поля, по которым нет локальных данных,
  отдаются `null` (а не отсутствуют) — клиент знает, что ожидать.
- `?fields=translations,audio_urls` — проекция корневых ключей
  (минимизирует трафик; `surah`/`ayah` всегда остаются).
- `?format=html|text|tree` — глобальный параметр форматирования HTML
  работает и тут (например, `tajweed_analysis` может содержать поля с
  HTML).
- `404` — если `ayah` больше `versesCount` суры
  (валидация через `data/surah-info-ru.json`).
- `tajweed_html` и `word_timing` — для них есть отдельные прокси:
  `GET /api/quran/verse/{s}/{a}` и `GET /api/quran/timings/...`. Они
  делают внешний HTTP-запрос и кэшируют в SQLite; bundle их не
  включает, чтобы оставаться полностью «локальным» (моментальный
  ответ).

---

## Поддержка

- Issue tracker: [GitHub Issues](https://github.com/KlayStoryte/tadjwid2/issues)
- Email: admin@e-replika.ru

## Лицензия контента

Образовательное использование. Атрибуция Wikimedia Commons + классические
источники таджвида (Тухфат аль-Атфаль, Мукаддима аль-Джазария).
